Upload
sang-don-kim
View
1.256
Download
9
Embed Size (px)
Citation preview
옥찬호 / 넥슨 (NEXON KOREA), Visual C++ MVP
녹슨 C++ 코드에 모던 C++로 기름칠하기
시작하기 전에…
• 모던 C++이란 C++11/14를 말합니다.
• C++11/14을 통한 개선 뿐만 아니라
기존 C++을 통한 개선 방법도 함께 포함합니다.
• 모던 C++을 모르는 분들을 위해 최대한 쉽게 설명합니다.
• 예제 코드가 많은 부분을 차지합니다.
녹슨 C++ 코드란?
int _output(FILE* stream,char const* format,va_list arguments)
{// ...
}
#ifdef _UNICODEint _woutput (#else /* _UNICODE */int _output (#endif /* _UNICODE */
FILE* stream,_TCHAR const* format,va_list arguments)
{// ...
}
#ifdef _UNICODE#ifdef POSITIONAL_PARAMETERSint _woutput_p (#else /* POSITIONAL_PARAMETERS */int _woutput (#endif /* POSITIONAL_PARAMETERS */#else /* _UNICODE */#ifdef POSITIONAL_PARAMETERSint _output_p (#else /* POSITIONAL_PARAMETERS */int _output (#endif /* POSITIONAL_PARAMETERS */#endif /* _UNICODE */
FILE* stream,_TCHAR const* format,va_list arguments)
{ ... }
IFileDialog *pfd = NULL;HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pfd));if (SUCCEEDED(hr)) {
IFileDialogEvents *pfde = NULL;hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));if (SUCCEEDED(hr)) {
DWORD dwCookie;hr = pfd->Advise(pfde, &dwCookie);if (SUCCEEDED(hr)) {
DWORD dwFlags;hr = pfd->GetOptions(&dwFlags);if (SUCCEEDED(hr)) {
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);if (SUCCEEDED(hr)) {
hr = pfd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes);if (SUCCEEDED(hr)) {
hr = pfd->SetFileTypeIndex(INDEX_WORDDOC);if (SUCCEEDED(hr)) {
hr = pfd->SetDefaultExtension(L"doc;docx");if (SUCCEEDED(hr)) {
고치고 싶다… 하지만
• 이미 고치기엔 길어져버린 코드
• 어디서부터 손을 써야 할 지 모름
• 코드는 점점 산으로…
• 아 귀찮다… ㅁㄴㅇㄹ
어디에 기름칠을 해볼까?
• 전처리기
• 리소스 관리
• 함수
• 타입, 반복문
• 기타 등등…
전처리기
조건부 컴파일
• #if, #ifdef, #ifndef, #elif, #else, …
• 많이 쓸수록 복잡해진다.
• 많이 쓸수록 이해하기 어렵다.
• 많이 쓸수록 유지보수하기 어렵다.
#ifdef _UNICODEint _woutput (#else /* _UNICODE */int _output (#endif /* _UNICODE */
FILE* stream,_TCHAR const* format,va_list arguments)
{// ...
}
template <typename T>static int common_output(
FILE* stream,T const* format,va_list arguments)
{// ...
}
int _output(FILE* stream, char const* format, va_list const arguments) {return common_output(stream, format, arguments);
}
int _woutput(FILE* stream, wchar_t const* format, va_list const arguments) {return common_output(stream, format, arguments);
}
// Check windows#if _WIN32 || _WIN64#if _WIN64#define ENVIRONMENT64#else#define ENVIRONMENT32#endif#endif
// Check GCC#if __GNUC__#if __x86_64__ || __ppc64__#define ENVIRONMENT64#else#define ENVIRONMENT32#endif#endif
케이스 바이 케이스
• 타입에 따른 조건부 컴파일은 함수 템플릿을 통해 개선한다.
• 하지만 #ifdef를 사용해야 되는 경우도 있다.
• 32비트 vs 64비트 코드
• DEBUG 모드 vs Non-DEBUG 모드
• 컴파일러, 플랫폼, 언어에 따라 다른 코드
• 반드시 사용해야 된다면, 코드를 단순화하는 것이 좋다.
• 중첩 #ifdef를 피하고, 함수의 일부를 조건부 컴파일에 넣지 않도록 한다.
매크로
• #define …
• 변수 대신 사용하는 매크로 : #define RED 1
• 함수 대신 사용하는 매크로 : #define SQUARE(x) ((x) * (x))
• 수많은 문제를 일으키는 장본인
• 컴파일러가 타입에 대한 정보를 갖기 전에 계산됨
• 필요 이상으로 많이 사용
변수 대신 사용하는 매크로
#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 declarederror C2143: syntax error : missing ';' before 'constant'error C2106: '=' : left operand must be l-value
#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 declarederror C2143: syntax error : missing ';' before 'constant'error C2106: '=' : left operand must be l-value
#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); // Okg(9000); // Not ok, but compiler can’t tell
}
enum color_type{
red = 0,orange = 1,yellow = 2,green = 3,blue = 4,purple = 5,hot_pink = 6
};
enum color_type{
red, orange, yellow, green, blue, purple, hot_pink};
void g(color_type color);void f(){
g(hot_pink); // Okg(9000); // Not ok, compiler will report error
}
error C2664: 'void g(color_type)' : cannot convert argument 1 from 'int' to 'color_type'
enum color_type{
red, orange, yellow, green, blue, purple, hot_pink};
void f(){
int x = red; // Ughint x = red + orange; // Double ugh
}
enum color_type{
red, orange, yellow, green, blue, purple, hot_pink};
enum traffic_light_state{
red, yellow, green};
error C2365: 'red' : redefinition; previous definition was 'enumerator‘error C2365: 'yellow' : redefinition; previous definition was 'enumerator‘error C2365: 'green' : redefinition; previous definition was 'enumerator'
열거체의 문제점
• 묵시적인 int 변환
• 열거체의 타입을 명시하지 못함
• 이상한 범위 적용
→ 열거체 클래스(enum class)의 등장!
enum class color_type{
red, orange, yellow, green, blue, purple, hot_pink};
void g(color_type color);
void f(){
g(color_type::red);}
enum class color_type{
red, orange, yellow, green, blue, purple, hot_pink};
void g(color_type color);
void f(){
int x = color_type::hot_pink;}
error C2440: 'initializing' : cannot convert from 'color_type' to 'int'
enum class color_type{
red, orange, yellow, green, blue, purple, hot_pink};
void g(color_type color);
void f(){
int x = static_cast<int>(color_type::hot_pink);}
열거체 클래스를 사용하자
• 묵시적인 int 변환
→ 명시적인 int 변환
• 열거체의 타입을 명시하지 못함
→ 타입 명시 가능
• 이상한 범위 적용
→ 범위 지정 연산자를 통해 구분
함수 대신 사용하는 매크로
#define make_char_lowercase(c) \((c) = (((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
void make_string_lowercase(char* s){
while (make_char_lowercase(*s++));
}
#define make_char_lowercase(c) \((c) = (((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))
void make_string_lowercase(char* s){
while (((*s++) = (((*s++) >= 'A') && ((*s++) <= 'Z'))? ((*s++) - 'A' + 'a') : (*s++)))
;}
// 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;}
열거체, 함수를 사용하자
• 변수 대신 사용하는 매크로에는 열거체를 사용하자.
• 열거체에서 발생할 수 있는 문제는 enum class로 해결할 수 있다.
• 열거체 대신 ‘static const’ 변수를 사용하는 방법도 있다.
• 함수 대신 사용하는 매크로에는 함수를 사용하자.
• 읽기 쉽고, 유지보수하기 쉽고, 디버깅하기 쉽다.
• 성능에 따른 오버헤드도 없다.
리소스 관리
IFileDialog *pfd = NULL;HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pfd));if (SUCCEEDED(hr)) {
IFileDialogEvents *pfde = NULL;hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));if (SUCCEEDED(hr)) {
DWORD dwCookie;hr = pfd->Advise(pfde, &dwCookie);if (SUCCEEDED(hr)) {
DWORD dwFlags;hr = pfd->GetOptions(&dwFlags);if (SUCCEEDED(hr)) {
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);if (SUCCEEDED(hr)) {
hr = pfd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes);if (SUCCEEDED(hr)) {
hr = pfd->SetFileTypeIndex(INDEX_WORDDOC);if (SUCCEEDED(hr)) {
hr = pfd->SetDefaultExtension(L"doc;docx");if (SUCCEEDED(hr)) {
IFileDialog *pfd = NULL;HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, IID_PPV_ARGS(&pfd));if (FAILED(hr))
return hr;
IFileDialogEvents *pfde = NULL;hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));if (FAILED(hr))
return hr;
DWORD dwCookie;hr = pfd->Advise(pfde, &dwCookie);if (FAILED(hr))
return hr;
DWORD dwFlags;hr = pfd->GetOptions(&dwFlags);if (FAILED(hr))
return hr;
}psiResult->Release();
}}
}}
}}
}pfd->Unadvise(dwCookie);
}pfde->Release();
}pfd->Release();
}
return hr;
\
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);}
\
RAII
• 자원 획득은 초기화다 (Resource Acquisition Is Initialization)
• 객체의 생성에 맞춰 메모리와 시스템 리소스를 자동으로 할당
• 객체의 소멸에 맞춰 메모리와 시스템 리소스를 자동으로 해제
→ 생성자 안에서 리소스를 할당하고, 소멸자에서 리소스를 해제
void ExampleWithRAII(){
// open file (acquire resource)File logFile("logfile.txt");
logFile.Write("Hello, Log File!");
// continue writing to logfile.txt ...}
File::File(const char* filename): m_file_handle(std::fopen(filename, "w+"))
{if (m_file_handle == NULL)
throw openError();}
File::~File(){
std::fclose(m_file_handle);}
void ExampleWithRAII(){
// open file (acquire resource)File* logFile = new File("logfile.txt");
logFile->Write("Hello, Log File!");
// continue writing to logfile.txt ...}
File::File(const char* filename): m_file_handle(std::fopen(filename, "w+"))
{if (m_file_handle == NULL)
throw openError();}
File::~File(){
std::fclose(m_file_handle);}
다시 발생하는 문제
• 파일 입출력과 관련한 예외 처리를 간편하게 하기 위해
File 클래스를 만들어 생성자와 소멸자로 처리했다.
• 하지만, 정작 File 클래스를 동적으로 할당하는 경우
소멸자가 호출되지 않아 파일을 닫지 않는 문제가 발생한다.
• 좋은 방법이 없을까?
→ 스마트 포인터(Smart Pointer)의 등장!
스마트 포인터
• 좀 더 똑똑한 포인터
• 스마트 포인터를 사용하면 명시적으로 해제할 필요가 없다.
• 사용하는 이유
• 적은 버그, 자동 청소, 자동 초기화
• Dangling 포인터 발생 X, Exception 안전
• 효율성
void ExampleWithRAII(){
// open file (acquire resource)std::unique_ptr<File> logFile = std::make_unique<File>("logfile.txt");
logFile->Write("Hello, Log File!");
// continue writing to logfile.txt ...}
File::File(const char* filename): m_file_handle(std::fopen(filename, "w+"))
{if (m_file_handle == NULL)
throw openError();}
File::~File(){
std::fclose(m_file_handle);}
스마트 포인터의 종류
• 경우에 따라 여러 종류의 스마트 포인터를 사용할 수 있다.
• shared_ptr : 객체의 소유권을 복사할 수 있는 포인터
(여러 shared_ptr 객체가 같은 포인터 객체를 가리킬 수 있음)
• unique_ptr : 객체의 소유권을 복사할 수 없는 포인터
(하나의 unique_ptr 객체만이 하나의 포인터 객체를 가리킬 수 있음)
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);
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");
}
std::shared_ptr
MyClass
제어 블록 참조 개수 = 1
개체에 대한 포인터
제어 블록에 대한 포인터
p1
std::shared_ptr
MyClass
제어 블록 참조 개수 = 2
개체에 대한 포인터
제어 블록에 대한 포인터
p1
개체에 대한 포인터
제어 블록에 대한 포인터
p2
// 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");
리소스 관리는 스마트 포인터로
• RAII를 사용하자!
• 읽고, 쓰고, 유지보수하기 쉽다.
• 자원 관리에 대한 걱정을 할 필요가 없다.
• C++ 코드 품질을 향상시키는 가장 쉬운 방법!
• 기왕이면 스마트 포인터로!
• shared_ptr
• unique_ptr
함수
std::vector<int>::const_iterator iter = cardinal.begin();std::vector<int>::const_iterator iter_end = cardinal.end();
int total_elements = 1;while (iter != iter_end){
total_elements *= *iter;++iter;
}
template <typename T>struct product {
product(T& storage) : value(storage) {}template<typename V>void operator()(V& v){
value *= v;}T& value;
};
std::vector<int> cardinal;
int total_elements = 1;for_each(cardinal.begin(), cardinal.end(), product<int>(total_elements));
int total_elements = 1;for_each(cardinal.begin(), cardinal.end(), [&total_elements](int i) {
total_elements *= i;});
struct mod
{
mod(int m) : modulus(m) {}
int operator()(int v)
{ return v % modulus; }
int modulus;
};
int my_mod = 8;
std::transform(in.begin(), in.end(),
out.begin(), mod(my_mod));
int my_mod = 8;
transform(in.begin(), in.end(), out.begin(),
[my_mod](int v) -> int
{ return v % my_mod; });
Functor Lambda Expression
람다식
[my_mod] (int v) -> int { return v % my_mod; }
개시자(Introducer Capture)
인자(Arguments)
반환 타입(Return Type)
함수의 몸통(Statement)
int x = 10, y = 20;
[] {}; // capture 하지 않음
[x] (int arg) { return x; }; // value(Copy) capture x
[=] { return x; }; // value(Copy) capture all
[&] { return y; }; // reference capture all
[&, x] { return y; }; // reference capture all except x
[=, &y] { return x; }; // value(Copy) capture all except y
[this] { return this->something; }; // this capture
[=, x] {}; // error
[&, &x] {}; // error
[=, this] {}; // error
[x, x] {}; // error
1
2
2
void fa(int x, function<void(void)> f) { ++x; f(); }
void fb(int x, function<void(int)> f) { ++x; f(x); }
void fc(int &x, function<void(void)> f) { ++x; f(); }
int x = 1;
fa(x, [x] { cout << x << endl; });
fb(x, [](int x) { cout << x << endl; });
fc(x, [&x] { cout << x << endl; });
WNDCLASSEX wcex;
wcex.lpfnWndProc= [](HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) ->
LRESULT {
switch (message) {
case WM_COMMAND:
EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL {
char szText[256];
GetWindowTextA(hwnd, szText, 256);
cout << szText << endl;
return TRUE;
}, 0);
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);
람다식을 사용하자
• 짧고, 간결하고, while 문과 같은 행사 코드 없이
깔끔하게 작성할 수 있다.
• 수십줄의 코드를 1~2줄로 간추릴 수 있다.
• Functor, Callback Function을 대체해서 사용할 수 있다.
• 반복적으로 사용하는 함수가 아니라면 람다식을 사용하자!
간단하게 적용 가능한 기능들
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) { … }
범위 기반 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;}
정리// circle and shape are user-defined typescircle* 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 match\n";
}
for (vector<circle*>::iterator i = v.begin(); i != v.end(); ++i) {delete *i; // not exception safe
}
delete p;
정리// circle and shape are user-defined typesauto 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 match\n";});
정리
• 대체할 수 있는 조건부 컴파일은 템플릿으로 기름칠!
• 매크로는 가급적 사용하지 말고 열거체와 함수로 기름칠!
• 리소스 관리에는 RAII, 기왕이면 스마트 포인터로 기름칠!
• 일회성으로 사용하는 함수는 람다식으로 기름칠!
• 복잡한 타입에는 auto로 기름칠!
• 반복 횟수에 고통받지 말고 범위 기반 for문으로 기름칠!
정리
• 모던 C++을 통해 대체할 수 있는 코드는 많습니다!
(하지만 제한된 시간으로 인해 …)
• 다음 사이트에서 모던 C++ 예제 코드를 확인하실 수 있습니다.
http://www.github.com/utilForever/ModernCpp
• C++ 핵심 가이드라인
• 영문 : https://github.com/isocpp/CppCoreGuidelines
• 한글 : https://github.com/CppKorea/CppCoreGuidelines
Quiz
#include <iostream>#include <memory>#include <vector>
class C {public:
void foo() { std::cout << "A"; }void foo() const { std::cout << "B"; }
};
struct S {std::vector<C> v;std::unique_ptr<C> u;C* const p;
S() : v(1), u(new C()), p(u.get()) {}};
Quiz #1int main() {
S s;const S& r = s;
s.v[0].foo(); s.u->foo(); s.p->foo();r.v[0].foo(); r.u->foo(); r.p->foo();
}
AAABAA
#include <iostream>
struct A {A() { std::cout << "A"; }A(const A& a) { std::cout << "B"; }virtual void f() { std::cout << "C"; }
};
int main() {A a[2];for (auto x : a) {
x.f();}
}
Quiz #2
AABCBC
#include <iostream>
struct A {A() { std::cout << "A"; }A(const A& a) { std::cout << "B"; }virtual void f() { std::cout << "C"; }
};
int main() {A a[2];for (auto& x : a) {
x.f();}
}
Quiz #3
AACC
C++ Korea
• http://www.facebook.com/groups/cppkorea
http://www.github.com/cppkorea
감사합니다.• MSDN Forum http://aka.ms/msdnforum
• TechNet Forum http://aka.ms/technetforum
http://aka.ms/td2015_again
TechDays Korea 2015에서 놓치신 세션은 Microsoft 기술 동영상 커뮤니티 Channel 9에서
추후에 다시 보실 수 있습니다.