프로그래밍 도서 추천: 이펙티브 모던 C++

오늘날 성능을 중요시하는 많은 소프트웨어는 C++를 사용합니다.
예를 들어,

  • 웹 브라우저: Google Chrome. 오늘날 거의 모든 웹 브라우저는 C++기반 응용 프로그램입니다. 모질라 재단의 Firefox처럼 부분적으로 다른 언어를 사용하는 곳도 있으나, C++를 주로 사용하는 점은 같습니다.
  • 기계 학습: Google TensorFlow 는 절반 이상의 기반 코드를 C++ 로 구성합니다.
  • 게임 클라이언트 엔진: Unreal Engine 4 역시 C++ 기반으로 만듭니다.

비슷하게, 아이펀팩토리에서 만드는 게임 서버 엔진인 아이펀 엔진 역시 코드의 근간은 C++로 되어있습다.

하지만 이런 C++언어는 사용하기 까다로운 편에 속합니다. 그래서 언어의 특징을 잘 활용하고 안전하게 사용하는 모법 사례들을 알아두는 것이 좋습니다. 이런 C++ 언어의 가장 좋은 사용법을 모아놓은 책을 찾아보자면 많은 분들이 스콧 마이어스 (Scott Meyers) 의 Effective C++ 시리즈를 추천합니다.
여기에서 다룬 내용들 중에 일부는 GCC 컴파일러 플래그가 되기도 했고, C++ 프로그래머 간에 의사소통할 때 적당한 기준점을 제시해주기도 합니다.

이 중 가장 최근에 나온 책이 이펙티브 모던 C++이고, 이 책에서는 최근까지 가장 큰 변경 사항이었던 C++11과 그 이후의 업데이트인 C++14 에 해당하는 내용을 다루고 있습니다.

위에서 언급한 프로젝트들 역시 대부분 C++11/14 기반으로 동작합니다. 그리고 사용 편의성이나 성능, 그리고 좀 더 최신 표준에 맞춰서 대형 소프트웨어 회사인 구글이나 페이스북의 기반 라이브러리들도 만듭니다.
(Google abseli는 C++11 기반입니다 Facebook Folly는 C++14 기반입니다.)

아이펀팩토리 내부와 고객사들이 한 질문을 토대로 크게 도움이 된 부분을 꼽아보면 아래와 같습니다.

C++11/14 소개

상당수의 책 내용이 이전에 C++98 표준을 가지고 작성한 부분을 어떻게 하면 C++11 에서 더 잘 다룰 수 있을지 설명하고 있습니다.
예를 들어 복사 생성이나 대입 연산자를 막고 싶은 경우에 C++98 용으로 아래와 같은 코드를 흔히 작성합니다.

class A : private boost::noncopyable<A&gt; {
  // ...
};

이 책의 “항목 17: 특수 멤버 함수들의 자동 작성 조건을 숙지하라” 의 내용을 쓰면 아래와 같이 수정할 수 있습니다.
이번엔 C++11에 추가된 우측값 참조를 써서 이동 생성자만 지원하는 형식으로 선언합니다.

class A {
 public:
  A(const A&) = delete;
  A& operator=(const A&) = delete;

  // 이동 생성만 허용하기
  A(A&&) = default;
};

비슷하게 C++11 이전 코드에선 “상속할 기반 클래스는 가상 소멸자가 필요하다” 였는데, 특별히 다뤄야할 추가 케이스가 없다면 매우 간단하게 구현할 수 있습니다.

class Base {
 public:
  // 별도의 메모리 해제 등이 필요하지 않으면 기본 구현으로 충분
  virtual ~Base() = default;
};

이런 방식으로 타입 선언으로 좀 더 쉽게해주는 auto 를 쓰도록 유도하는 “항목 5: 명시적인 형식 선언보다는 auto를 선언하라” 라거나, 이에 대한 기반 지식을 설명하는
“항목 2: auto의 형식 연역 규칙을 숙지하라” 그리고 라이브러리를 만들 생각이라면 비슷하게 도움이될 “항목 3: decltype의 작동 방식을 숙지하라” 도 있습니다.

하나 덧붙이자면, 이전부터 MSVC 기반으로 작성하면 널리 사용하는 “오버라이드한 멤버 함수에 override 키워드 쓰기” 에 해당하는 부분을 설명하는 “항목 12: 재정의 함수들을 override로 선언하라”도 한 번 읽어보고 적용할만 합니다.

스마트 포인터

많은 사람들이 C++11을 사용하지 않는 경우에도 boost::shared_ptr<T> 처럼 참조 횟수를 세는 방식의 스마트 포인터를 사용합니다. 여기서 한 걸음 더 나아가서, C++11 부터는 날 포인터 (raw pointer) 를 쓸 이유가 거의 없는데, 이에 대해서 자세히 설명합니다.

예를 들어, C++11 이전이라면 클래스 생성자/소멸자에서 메모리를 할당하고 해제하는게 일반적입니다. 그러나 이제는 std::unique_ptr<T> 로 처리하는게 더 좋습니다. (성능상 손해도 거의 없다고 볼 수 있습니다)

람다 표현식

게임 서버나 다른 비동기 처리가 필요한 부분을 다루다보면, 많은 곳에서 콜백 함수를 활용하게 됩니다. 이런 콜백 함수를 별도의 선언이나 정의를 분리해서 작성하는게 아니라, 좀 더 편하게, 그리고 실제로 콜백을 사용하는 장소 근처에 정의할 수 있는 C++의 새 기능으로 람다 표현식이 있습니다. 그리고 이에 대해서 C++의 기본 동작 방식 대신에 어떻게 외부 변수를 다루는게 좋은지 (“항목 31: 기본 갈무리 모드를 피하라”), 그리고 람다가 만들어내는 클로저 안에 저장할 값을 위한 “항목 32: 객체를 클로저 안으로 이동하려면 초기화 갈무리를 사용하라” 도 설명합니다. (마지막 내용은 C++14 기반입니다)

이 부분을 읽고나면 C++에서 콜백 함수를 다루는 일이 한결 간결해지고, 메모리나 객체 수명을 더 안전하게 관리할 수 있게 됩니다.

라이브러리 제작자를 위해서

다른 팀원 또는 팀이 사용할 인터페이스나 라이브러리를 만드는 경우, 최신 표준을 지원하는 컴파일러를 사용할 수 있다면, C++의 철학인 “사용하지 않는 것의 비용을 지불하지 않는다” 를 최대한 실천해봄직합니다. 높은 성능이나 낮은 지연 시간처럼 C++로 잘 할 수 있는 부분이 필요하다면, 아래 항목들이 큰 도움이 될 수 있습니다.

오른값 참조 / 이동 생성자

C++ 객체 생성이나 복사는 엄청나게 복잡한 처리가 필요할 수 있습니다. C++11 이후부터는 이런 경우를 위해서 복잡한 객체를 부하 없이 옮길 수 있는 오른값 참조라는 방법을 제공합니다. 이런 방법을 쓰면 라이브러리 / 프로그램 경계에서 객체를 복사할 때의 낭비를 상당 수 막을 수 있습니다. 이 부분에 대해서는 항목 23부터 30까지 여러 개의 항목을 써서 설명하고 있습니다.

상수 표현식 (constexpr)

컴파일 시간에 값을 계산할 수 있다면, 이걸 미리 다 계산하고, 컴파일러가 이 값들을 미리 상수 값 전파나 코드를 미리 풀어버리면 상당한 성능 상의 잇점이 있습니다. 이에 대해서도 “항목 15: 가능하면 항상 constexpr을 사용하라” 에서 설명합니다.

decltype

템플릿 라이브러리나 좀 더 일반적인 라이브러를 작성하다보면, 반환 타입이나 인자의 타입을 의존적 타입 (dependent type) 으로 표현해야하는 번거로운 경우를 자주 만나게 됩니다.
이런 경우에도 decltype 을 사용하면 좀 더 쉽게 범용적인 코드를 작성할 수 있습니다. 이에 대한 설명과 타입 연역 방식에 대해서 “항목 4: 연역된 형식을 파악하는 방법을 알아두라”에서 자세히 설명하고 있습니다.

맺는 말

이펙티브 모던 C++은 이제 정말 널리 쓰이기 시작한 C++ 표준에 대한 좋은 안내서입니다. 더 나은 성능과 안전한 프로그램 제어를 위해서 C++을 사용한다면, 이 책을 꼭 읽어보는게 좋겠습니다.

답글 남기기

댓글을 게시하려면 다음의 방법 중 하나를 사용하여 로그인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중

This site uses Akismet to reduce spam. Learn how your comment data is processed.