기존에 작성해 놓은 C++ 코드에 모던 C++를 적용하기는 쉽지 않습니다. 막상 개선하려고 마음먹었다고 해도, 어디서부터 바꿔야 할 지 막막하기만 합니다. 이 세션에서는 기존 C++ 코드에서 모던 C++를 적용해 프로그램의 구조와 성능을 개선하는 방법에 대해서 설명합니다. 그리고 기존 C++ 코드에 모던 C++를 적용할 때 주의해야 될 점에 대해서도 살펴봅니다.
17. 케이스 바이 케이스
• 타입에 따른 조건부 컴파일은 함수 템플릿을 통해 개선한다.
• 하지만 #ifdef를 사용해야 되는 경우도 있다.
• 32비트 vs 64비트 코드
• DEBUG 모드 vs Non-DEBUG 모드
• 컴파일러, 플랫폼, 언어에 따라 다른 코드
• 반드시 사용해야 된다면, 코드를 단순화하는 것이 좋다.
• 중첩 #ifdef를 피하고, 함수의 일부를 조건부 컴파일에 넣지 않도록 한다.
18. 매크로
• #define …
• 변수 대신 사용하는 매크로 : #define RED 1
• 함수 대신 사용하는 매크로 : #define SQUARE(x) ((x) * (x))
• 수많은 문제를 일으키는 장본인
• 컴파일러가 타입에 대한 정보를 갖기 전에 계산됨
• 필요 이상으로 많이 사용
20. #define red 0
#define orange 1
#define yellow 2
#define green 3
#define blue 4
#define purple 5
#define hot_pink 6
void f()
{
unsigned orange = 0xff9900;
}
warning C4091: '' : ignored on left of 'unsigned int' when no variable is declared
error C2143: syntax error : missing ';' before 'constant'
error C2106: '=' : left operand must be l-value
21. #define red 0
#define orange 1
#define yellow 2
#define green 3
#define blue 4
#define purple 5
#define hot_pink 6
void f()
{
unsigned 2 = 0xff00ff;
}
warning C4091: '' : ignored on left of 'unsigned int' when no variable is declared
error C2143: syntax error : missing ';' before 'constant'
error C2106: '=' : left operand must be l-value
22. #define RED 0
#define ORANGE 1
#define YELLOW 2
#define GREEN 3
#define BLUE 4
#define PURPLE 5
#define HOT_PINK 6
void g(int color); // valid values are 0 through 6
void f()
{
g(HOT_PINK); // Ok
g(9000); // Not ok, but compiler can’t tell
}
23. enum color_type
{
red = 0,
orange = 1,
yellow = 2,
green = 3,
blue = 4,
purple = 5,
hot_pink = 6
};
24. enum color_type
{
red, orange, yellow, green, blue, purple, hot_pink
};
void g(color_type color);
void f()
{
g(hot_pink); // Ok
g(9000); // Not ok, compiler will report error
}
error C2664: 'void g(color_type)' : cannot convert argument 1 from 'int' to 'color_type'
25. enum color_type
{
red, orange, yellow, green, blue, purple, hot_pink
};
void f()
{
int x = red; // Ugh
int x = red + orange; // Double ugh
}
35. // Old, ugly macro implementation:
#define make_char_lowercase(c)
((c) = (((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
// New, better function implementation:
inline char make_char_lowercase(char& c)
{
if (c > 'A' && c < 'Z')
{
c = c - 'A' + 'a';
}
return c;
}
36. 열거체, 함수를 사용하자
• 변수 대신 사용하는 매크로에는 열거체를 사용하자.
• 열거체에서 발생할 수 있는 문제는 enum class로 해결할 수 있다.
• 열거체 대신 ‘static const’ 변수를 사용하는 방법도 있다.
• 함수 대신 사용하는 매크로에는 함수를 사용하자.
• 읽기 쉽고, 유지보수하기 쉽고, 디버깅하기 쉽다.
• 성능에 따른 오버헤드도 없다.
41. void ExampleWithoutRAII() {
std::FILE* file_handle = std::fopen("logfile.txt", "w+");
if (file_handle == nullptr)
throw std::runtime_error("File couldn't open!");
try {
if (std::fputs("Hello, Log File!", file_handle) == EOF)
throw std::runtime_error("File couldn't write!");
// continue writing to logfile.txt ... do not return
// prematurely, as cleanup happens at the end of this function
}
catch (...)
{
std::fclose(file_handle);
throw;
}
std::fclose(file_handle);
}
42. RAII
• 자원 획득은 초기화다 (Resource Acquisition Is Initialization)
• 객체의 생성에 맞춰 메모리와 시스템 리소스를 자동으로 할당
• 객체의 소멸에 맞춰 메모리와 시스템 리소스를 자동으로 해제
→ 생성자 안에서 리소스를 할당하고, 소멸자에서 리소스를 해제
45. 다시 발생하는 문제
• 파일 입출력과 관련한 예외 처리를 간편하게 하기 위해
File 클래스를 만들어 생성자와 소멸자로 처리했다.
• 하지만, 정작 File 클래스를 동적으로 할당하는 경우
소멸자가 호출되지 않아 파일을 닫지 않는 문제가 발생한다.
• 좋은 방법이 없을까?
→ 스마트 포인터(Smart Pointer)의 등장!
46. 스마트 포인터
• 좀 더 똑똑한 포인터
• 스마트 포인터를 사용하면 명시적으로 해제할 필요가 없다.
• 사용하는 이유
• 적은 버그, 자동 청소, 자동 초기화
• Dangling 포인터 발생 X, Exception 안전
• 효율성
48. 스마트 포인터의 종류
• 경우에 따라 여러 종류의 스마트 포인터를 사용할 수 있다.
• shared_ptr : 객체의 소유권을 복사할 수 있는 포인터
(여러 shared_ptr 객체가 같은 포인터 객체를 가리킬 수 있음)
• unique_ptr : 객체의 소유권을 복사할 수 없는 포인터
(하나의 unique_ptr 객체만이 하나의 포인터 객체를 가리킬 수 있음)
49. std::unique_ptr
ptrA Song 개체
ptrA Song 개체
ptrB
auto ptrA = std::make_unique<Song>(L"Diana Krall", L"The Look of Love");
auto ptrB = std::move(ptrA);
50. std::unique_ptr<Song> SongFactory(const std::wstring& artist, const std::wstring& title)
{
// Implicit move operation into the variable that stores the result.
return std::make_unique<Song>(artist, title);
}
void MakeSongs()
{
// Create a new unique_ptr with a new object.
auto song = std::make_unique<Song>(L"Mr. Children", L"Namonaki Uta");
// Use the unique_ptr.
std::vector<std::wstring> titles = { song->title };
// Move raw pointer from one unique_ptr to another.
std::unique_ptr<Song> song2 = std::move(song);
// Obtain unique_ptr from function that returns by value.
auto song3 = SongFactory(L"Michael Jackson", L"Beat It");
}
53. // Use make_shared function when possible.
auto sp1 = std::make_shared<Song>(L"The Beatles", L"Im Happy Just to Dance With You");
// Ok, but slightly less efficient.
// Note: Using new expression as constructor argument
// creates no named variable for other code to access.
std::shared_ptr<Song> sp2(new Song(L"Lady Gaga", L"Just Dance"));
// When initialization must be separate from declaration, e.g. class members,
// initialize with nullptr to make your programming intent explicit.
std::shared_ptr<Song> sp5(nullptr);
//Equivalent to: shared_ptr<Song> sp5;
//...
sp5 = std::make_shared<Song>(L"Elton John", L"I'm Still Standing");
54. 리소스 관리는 스마트 포인터로
• RAII를 사용하자!
• 읽고, 쓰고, 유지보수하기 쉽다.
• 자원 관리에 대한 걱정을 할 필요가 없다.
• C++ 코드 품질을 향상시키는 가장 쉬운 방법!
• 기왕이면 스마트 포인터로!
• shared_ptr
• unique_ptr
64. HANDLE hT = CreateThread(NULL, 0, [](LPVOID lpThreadParameter) -> DWORD {
for (int i = 0; i < 1000; i++) {
this_thread::sleep_for(milliseconds{ 10 });
cout << i << endl;
}
return 0;
}, NULL, 0, NULL);
65. 람다식을 사용하자
• 짧고, 간결하고, while 문과 같은 행사 코드 없이
깔끔하게 작성할 수 있다.
• 수십줄의 코드를 1~2줄로 간추릴 수 있다.
• Functor, Callback Function을 대체해서 사용할 수 있다.
• 반복적으로 사용하는 함수가 아니라면 람다식을 사용하자!
67. auto 키워드
• 컴파일 타임에 타입을 추론해 어떤 타입인지 결정한다.
• 컴파일 타임에 추론이 불가능하다면, 오류가 발생한다.
std::vector<std::tuple<std::string, int, double>> vStudents;
for (std::vector<std::tuple<std::string, int, double>>::iterator iter =
vStudents.begin(); iter != vStudents.end(); ++iter) { … }
std::vector<std::tuple<std::string, int, double>> vStudents;
for (auto iter = vStudents.begin(); iter != vStudents.end(); ++iter) { … }
68. 범위 기반 for문
int arr[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < 5; ++i)
std::cout << arr[i] << std::endl;
return 0;
}
int arr[] = { 1, 2, 3, 4, 5 };
for (auto& i : arr)
std::cout << i << std::endl;
return 0;
}
69. 정리
// circle and shape are user-defined types
circle* p = new circle(42);
vector<shape*> v = load_shapes();
for (vector<circle*>::iterator i = v.begin(); i != v.end(); ++i) {
if (*i && **i == *p)
cout << **i << " is a matchn";
}
for (vector<circle*>::iterator i = v.begin(); i != v.end(); ++i) {
delete *i; // not exception safe
}
delete p;
70. 정리
// circle and shape are user-defined types
auto p = make_shared<circle>(42);
vector<shared_ptr<shape>> v = load_shapes();
for_each(begin(v), end(v), [&](const shared_ptr<shape>& s) {
if (s && *s == *p)
cout << *s << " is a matchn";
});
71. 정리
• 대체할 수 있는 조건부 컴파일은 템플릿으로 기름칠!
• 매크로는 가급적 사용하지 말고 열거체와 함수로 기름칠!
• 리소스 관리에는 RAII, 기왕이면 스마트 포인터로 기름칠!
• 일회성으로 사용하는 함수는 람다식으로 기름칠!
• 복잡한 타입에는 auto로 기름칠!
• 반복 횟수에 고통받지 말고 범위 기반 for문으로 기름칠!
72. 정리
• 모던 C++을 통해 대체할 수 있는 코드는 많습니다!
(하지만 제한된 시간으로 인해 …)
• 다음 사이트에서 모던 C++ 예제 코드를 확인하실 수 있습니다.
http://www.github.com/utilForever/ModernCpp
• C++ 핵심 가이드라인
• 영문 : https://github.com/isocpp/CppCoreGuidelines
• 한글 : https://github.com/CppKorea/CppCoreGuidelines