10 Jun 2020
|
C++
모던 C++ 입문 책을 읽고 실수하기 쉽거나 유용한 팁, 더 나은 방법 등을 정리해보았습니다.
포인터
포인터는 메모리 주소 값을 가지는 변수입니다. 포인터를 사용하면 강력한 일들을 수행할 수 있지만 메모리 누수(Memory Leak)와 같은
위험이 자주 발생할 수 있어서 점점 사용을 제한하는 추세입니다.
포인터 관련 오류를 최소화하기 위해서 다음과 같은 방법들이 있습니다.
std::vector와 같은 표준 컨테이너를 사용해라.
- 클래스에서 동적 메모리는 개체 생성시 할당하고 파괴할 때 해제해야 한다. 이러한 원칙을 RAII(Resource Acquisition Is Initialization)이라고 합니다.
- 스마트 포인터를 사용해라.
- 레퍼런스(Reference)를 사용해라.
NULL 매크로 대신 nullptr을 사용해라.
스마트 포인터
개인적으로 C++11로 넘어오면서 가장 큰 변화가 스마트 포인터와 Thread 표준화가 아닌가 생각이 듭니다.
unique_ptr, shared_ptr, weak_ptr이 있습니다.
unique_ptr
데이터의 고유 소유권(Unique Ownership)을 나타내며, 포인터 만료시 메모리가 자동 해제됩니다.
int main() {
unique_ptr<double> dp{ new double };
*dp = 7;
// ...
}
다른 포인터 타입에 할당하거나 암시적 변환은 불가능하며, 원시 포인터 데이터를 얻고 싶을 경우 `get()` 함수를 이용하면 됩니다.
double * raw_dp = dp.get();
다른 `unique_ptr`에 할당할 수도 없으며, 오직 이동(`move`)만 가능합니다.
unique_ptr<double> dp2{ move(dp) }, dp3;
dp3 = move(dp2);
위에서 참조한 메모리의 소유권을 `dp`에서 `dp2`로 전달한 다음 `dp3`에 전달합니다.
그 이후 `dp`와 `dp2`는 `nullptr`이 됩니다.
### shared_ptr
`shared_ptr`은 일반적으로 가장 많이 사용하게 될 스마트 포인터입니다.
### weak_ptr
`shared_ptr`에서 발생할 수 있는 문제 중 하나는 순환 참조(Cycle Reference)입니다. 순환 참조가 발생하면
메모리 해제가 되지 않아 메모리 누수(Memory Leak)이 발생할 수 있습니다. `weak_ptr`은 공유를 하더라도
소유권을 주장하지 않기 때문에 순환 참조를 막을 수 있습니다.
## 레퍼런스
레퍼런스가 포인터에 비해 갖는 주요 이점 중 하나는 동적 메모리 관리 및 주소 계산 기능입니다.
포인터에 비해 메모리 누수 가능성이 거의 없고, 포인터에 비해 표기법이 깔끔한 장점이 있습니다.
특징 | 포인터 | 레퍼런스
---|---|---
정의된 위치 참조 | | O
초기화 필수 | | O
메모리 누수 예방 | | O
개체와 같은 표기법 | | O
메모리 관리 | O |
주소 계산 | O |
컨테이너 만들기 | O |
### Stale Reference 및 Dangline Pointer
함수 내의 지역 변수는 함수 범위(Scope) 내에서만 유효합니다.
아래와 같은 코드는 절대 사용하지 않도록 합시다.
double & square_ref(double d) {
double s = d * d;
return s;
}
또는
double * square_ref(double d) {
double s = d * d;
return &s;
}
## 벡터 초기화
벡터 초기화는 요소별로 값을 설정하는 것보다 C++11부터 지원하는 Initializer List를 이용해서 초기화하는 것이 더 좋습니다.
vector<float> v = {1, 2, 3};
## 매크로
매크로는 대부분의 언어에서 최소한으로 제한하는 것이 좋습니다. 매크로는 이름을 인수와 함께 텍스트 정의 확장해서 코드를 재사용하는
고전 기법 중 하나일 뿐입니다.
대부분의 매크로는 `const`, `inline`, `constexpr` 등으로 대체해서 사용할 수 있습니다.
### include
`include` 전처리기는 `/usr/include`, `/usr/local/include` 등과 같은 표준 디렉토리에서 파일을 검색합니다.
컴파일러 옵션을 이용해서 디렉토리를 추가할 수도 있습니다.
만약 `include` 구문에 큰 따옴표를 쓰게 되면, 일반적으로 컴파일러는 현재 디렉토리에서 먼저 검색한 다음 표준 경로에서 검색합니다.
부등호는 시스템 헤더, 큰 따옴표는 사용자 헤더에 사용해야 한다고 주장하는 사람들도 있습니다.
자주 사용되는 헤더 파일이 프로젝트 내에서 여러 번 호출되는 것을 방지하기 위해서 `#ifndef` 등의 포함 방지(Include Guard) 매크로를
사용할 수 있습니다. 좀 더 편리하게 사용하는 방법으로 `#progma once`가 있으며, `progma`는 표준이 아니지만
대부분의 컴파일러가 지원하고 있습니다.
조건부 컴파일인 `#ifdef`, `#else` 등은 소스 코드 관리 및 테스트가 어려워지기 때문에 가급적 사용하지 않는 것이 좋습니다.
08 Jun 2020
|
Flutter
todo_item.dart
class TodoItem {
String name = '';
bool isChecked = false;
TodoItem({this.name});
}
main.dart
import 'package:fileio/todo_item.dart';
import 'package:flutter/material.dart';
void main() => runApp(SnowApp());
class SnowApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todo list',
theme: ThemeData(
primaryColor: Colors.deepPurple,
),
home: Scaffold(
appBar: AppBar(
title: Text('Todo list'),
),
body: TodoListWidget(),
));
}
}
class TodoListWidget extends StatefulWidget {
@override
State createState() => TodoListWidgetState();
}
class TodoListWidgetState extends State<TodoListWidget> {
final controller = TextEditingController();
final list = List();
@override
void dispose() {
super.dispose();
controller.dispose();
}
void addTodo(TodoItem item) {
setState(() {
list.add(item);
});
}
void removeTodo(TodoItem item) {
setState(() {
list.remove(item);
});
}
void setChecked(TodoItem item, bool isChecked) {
setState(() {
item.isChecked = isChecked;
});
}
Widget buildListTime(BuildContext context, TodoItem item) {
return ListTile(
onTap: () {
setChecked(item, !item.isChecked);
},
leading: item.isChecked == true
? Icon(Icons.check_box)
: Icon(Icons.check_box_outline_blank),
title: Text(
item.name,
style: item.isChecked
? TextStyle(
decoration: TextDecoration.lineThrough,
fontStyle: FontStyle.italic,
)
: null,
),
trailing: IconButton(
icon: Icon(Icons.delete_forever),
onPressed: () {
removeTodo(item);
},
),
);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
Expanded(
child: TextField(
controller: controller,
),
),
RaisedButton(
child: Text(
'Add',
),
onPressed: () {
addTodo(TodoItem(name: controller.text));
controller.text = '';
},
),
],
),
Expanded(
child: ListView(
children: list.map((item) => buildListTime(context, item)).toList(),
),
)
],
);
}
}
07 Jun 2020
|
Flutter
TextFile 쓰기/읽기
import 'dart:io';
import 'package:flutter/material.dart';
void main() => runApp(SnowApp());
class SnowApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'File IO Example',
theme: ThemeData(
primaryColor: Colors.indigoAccent,
),
home: Scaffold(
appBar: AppBar(
title: Text('File IO Example'),
),
body: FileIoTest(),
),
);
}
}
class FileIoTest extends StatelessWidget {
void saveToFile(String filepath, String text) {
final file = File(filepath);
file.createSync();
file.writeAsStringSync(text, mode: FileMode.append);
}
void loadFromFile(String filepath) {
final file = File(filepath);
print('Filepath: ${file.absolute.path}');
final lines = file.readAsLinesSync();
for (String line in lines) {
print(line);
}
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
RaisedButton(
child: Text('Save to file'),
onPressed: () {
final filepath = 'snowdeer.txt';
final text = "Hello, SnowDeer\n";
saveToFile(filepath, text);
},
),
RaisedButton(
child: Text('Load from file'),
onPressed: () {
final filepath = 'snowdeer.txt';
loadFromFile(filepath);
},
),
],
),
);
}
}
07 Jun 2020
|
Flutter
openFile, saveFile Dialog 사용 예제
먼저 pubspec.yaml 파일에 다음 항목을 추가해줍니다.
pubspec.yaml
dependencies:
file_chooser: ^0.1.2
path_provider: ^1.6.10
path_provider_macos: ^0.0.4+3
여기서 file_chooser는 구글에서 만든 파일을 선택할 수 있는 인터페이스를 제공해주는 라이브러리이며,
path_provider는 Documents와 같은 특정 디렉토리를 쉽게 찾을 수 있도록 함수를 제공해주는 라이브러리입니다.
여기서 테스트하는 App은 MacOS 버전이기 때문에 macos/Runner/DebugProfile.entitlements 파일에 다음 Permission도 추가해줍니다.
macos/Runner/DebugProfile.entitlements
<dict>
...
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
main.dart
import 'dart:io';
import 'package:file_chooser/file_chooser.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
appBar: AppBar(
title: Text('File Dialog Example'),
),
body: SnowDeerExampleWidget(),
),
);
}
}
class SnowDeerExampleWidget extends StatefulWidget {
@override
State createState() => SnowDeerExampleWidgetState();
}
class SnowDeerExampleWidgetState extends State<SnowDeerExampleWidget> {
void showOpenDialog() async {
String initDirectory;
if (Platform.isMacOS || Platform.isWindows) {
initDirectory = (await getApplicationDocumentsDirectory()).path;
}
showOpenPanel(
allowsMultipleSelection: true,
initialDirectory: initDirectory,
).then((value) {
final paths = value.paths;
for (int i = 0; i < paths.length; i++) {
final path = paths[i];
print('- path: $path');
}
});
}
void showSaveDialog() {
showSavePanel().then((value) {
final paths = value.paths;
for (int i = 0; i < paths.length; i++) {
final path = paths[i];
print('- path: $path');
}
});
}
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Column(
children: [
RaisedButton(
child: Text('Show open dialog'),
onPressed: () {
showOpenDialog();
},
),
RaisedButton(
child: Text('Show save dialog'),
onPressed: () {
showSaveDialog();
},
),
],
),
),
);
}
}
05 Jun 2020
|
Flutter
Flutter 기존 프로젝트에 MacOS 실행 환경 추가
기존에 만들어진 Flutter 프로젝트에 MacOS 실행 환경을 추가하는 방법입니다.
리눅스 PC에서 생성한 Flutter 프로젝트를 MacOS에서 실행하면 처음에 다음과 같은 메시지가 발생합니다.
flutter run -d macos
Launching lib/main.dart on macOS in debug mode...
Exception: No macOS desktop project configured. See
https://flutter.dev/desktop#add-desktop-support-to-an-existing-flutter-project to learn about adding
macOS support to a project.
해결법
flutter create . 명령어를 실행하면 됩니다.
$ flutter create .
Recreating project ....
snowdeer_flutter_sample.iml (created)
macos/Runner.xcworkspace/contents.xcworkspacedata (created)
macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (created)
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (created)
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (created)
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (created)
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (created)
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (created)
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (created)
macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (created)
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (created)
macos/Runner/DebugProfile.entitlements (created)
macos/Runner/Base.lproj/MainMenu.xib (created)
macos/Runner/MainFlutterWindow.swift (created)
macos/Runner/Configs/Debug.xcconfig (created)
macos/Runner/Configs/Release.xcconfig (created)
macos/Runner/Configs/Warnings.xcconfig (created)
macos/Runner/Configs/AppInfo.xcconfig (created)
macos/Runner/AppDelegate.swift (created)
macos/Runner/Info.plist (created)
macos/Runner/Release.entitlements (created)
macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (created)
macos/Runner.xcodeproj/project.pbxproj (created)
macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (created)
macos/Flutter/Flutter-Debug.xcconfig (created)
macos/Flutter/Flutter-Release.xcconfig (created)
macos/.gitignore (created)
android/snowdeer_flutter_sample_android.iml (created)
.idea/runConfigurations/main_dart.xml (created)
.idea/libraries/KotlinJavaRuntime.xml (created)
Wrote 33 files.
All done!