2 minute read

다시 C++을 만지게 됐다. 책장에 꽂혀 있던 ’Effective Modern C++’ 책을 다시 읽었다. 예전에 읽었던 것 같은데, 새롭고 놀랐다. 나보다 더 깊이 생각한다. 여기까지 설명하겠지 하면서 읽다 보면 예상한 것보다 한 발짝 더 깊이 들어가서 설명한다. 스콧 마이어스 책이 이래서 재미있다.

아래는 재미있게 본 항목에 대한 간단한 소감이다.

항목 1: 템플릿 타입 연역(Type deduction) 규칙을 숙지하라, 항목 2: auto의 형식 연역 규칙을 숙지하라

auto를 설명하려고 템플릿 타입 연역(Type deduction)부터 시작하는 게 스콧 마이어스답다고 생각했다. 타입 연역을 설명한 항목이 가장 유익했다.

항목 5: 명시적 형식 선언보다는 auto를 선호하라

컴파일러만 알던 타입을 지정할 수 있어서 클로저 타입을 정의할 수 있다.

이제 클로저(Closure)를 auto 타입으로 정의할 수 있다고 간단하게 생각했다. 이런 설명은 내게 다른 시각이다. C++에 있어 auto는 꽤 중요한 피처라는 생각이 들었다. auto는 컴파일러만 알던 타입을 정의할 수 있게 해줘서 클로저 타입 정의가 가능하게 한다.

항목 7: 객체 생성 시 괄호(())와 중괄호({})를 구분하라

일관되게 사용하는 건 당연하다. 괄호를 디폴트로 사용할 때와 중괄호를 디폴트로 사용할 때, 염두에 둬야 할 것들을 정리한 게 좋았다.

항목 8: 0과 NULL보다 nullptr를 선호하라

Overloading resolve 문제를 얘기하면서 nullptr를 선호해야 하는 반박하지 못하는 이유를 설명한다. 이런 설명은 기대하지 않아서 재미있었다.

항목 12: 재정의 함수들을 override로 선언하라

C++11에서 Overriding 함수 정의 조건에 참조 한정사(Reference qualifier)가 추가됐다. 참조 한정사까지 같아야 overriding 된다. 의도와 다를 때, 컴파일러가 워닝을 내주기도 하고 그냥 넘어가기도 한다. override 키워드를 붙이면 의도와 다르게 정의했을 때, 에러를 낸다. 이래도 override를 안 붙일래?

항목 13: iterator보다 const_iterator를 선호하라

예전에 const iterator를 써보려다가 너무 번거로워서 안 썼던 기억이 어렴풋이 난다. 불편한 것들이 C++11, C++14에서 추가됐다. 이제 const_iterator를 써야 하는 곳에 그냥 쓰면 된다.

항목 17: 특수 멤버 함수들의 자동 작성 조건을 숙지하라

3의 법칙(Rule of Three)이 있다. 다음 중 하나라도 구현하면 다 구현해야 한다.

  • 복사 생성자
  • 복사 할당 연산자
  • 소멸자

구현해야 할 특수 멤버 함수 개수를 보니 우울해진다.

  • C++98
    • 기본 생성자
    • 소멸자
    • 복사 생성자
    • 복사 할당 연산자
  • C++11
    • 이동 생성자
    • 이동 할당 생성자

항목 21: new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호하라

shared_ptr은 제어 블럭(control block)을 사용한다. std::make_shared 함수는 제어 블럭까지 인접한 메모리 공간에 한 번에 할당해서 최적화에도 유리하다.

항목 22: Pimpl 관용구를 사용할 때는 특수 멤버 함수들을 구현 파일에서 정의하라

이런 문제를 겪은 적이 있는 것 같다. 어떻게 해결했는지 기억나지 않는다. 다만 컴파일러 에러 메시지가 암호 같아서 도움이 되지 않았던 것은 확실하다. 컴파일러가 완전한 타입으로 인식해야 하는 타이밍에 타입 정의 코드를 보지 못해서 발생하는 문제다.

항목 23: std::move와 std::forward를 숙지하라

std::move를 std::rvalue_cast 로 이름 짓고 std::forward를 std::maybe_rvalue_cast 라고 이름을 지었으면 이 단락은 없었을지도 모르겠다

항목 26: 보편 참조에 대한 중복적재를 피하라, 항목 27: 보편 참조에 대한 중복적재 대신 사용할 수 있는 기법들을 알아두라

Overloading이 의도대로 되지 않아서 피해야 하는 상황을 설명한다. 여기서 끝나지 않는다. 다음 항목에서 정 써야 하겠다면 어떻게 사용할 수 있는지 기법들을 설명한다. 훌륭하다.

스콧 마이어스는 보편 참조(Universal Reference)라는 용어를 밀었지만 전달 참조(Forwarding Reference)라는 용어가 표준이 됐다. N4164

항목 28: 참조 축약을 숙지하라

참조에 대한 참조는 위법이다. 하지만 특정 문맥에서 컴파일러가 참조에 대한 참조를 산출하는 건 허용한다. 두 참조 중 하나라도 왼값 참조이면 결과는 왼값 참조이고 둘 다 오른값 참조이면 결과는 오른값 참조이다. 이걸 참조 축약(reference collapsing)이라고 한다. 참조 축약이라는 용어를 배웠다.

항목 37: std::thread들을 모든 경로에서 합류 불가능하게 만들어라

왜 std::thread 소멸자가 호출되면 프로그램 실행이 종료되는가? 더 나쁜 두 가지 대안밖에 없기 때문이다. 암묵적인 join, detach는 프로그램 실행 종료보다 더 나쁘다. 합리적인 결정이다.

항목 39: 단발성 사건(event) 통신에는 void 미래 객체를 고려하라

새삼 다시 느낀다. promise와 future 추상화는 훌륭하다.