이펙티브 모던 C++ 정리

WSong·2020년 8월 21일
0
post-custom-banner

1장 형식 연역

항목 1: 템플릿 형식 연역 규칙을 숙지하라

template T와 ParamType T의 형식이 달라질 수 있으니 잘 숙지해야 한다. 특히 const와 &가 사용된 경우 그러하다.

항목 2: auto의 형식 연역 규칙을 숙지하라

기본적으로 템플릿과 동일하다. 단, { 1 } 형식의 값은 std::initializer_list가 된다.

항목 3: decltype의 작동 방식을 숙지하라

template에서 decltype으로 인자와 동일한 형태를 반환하는 등의 방법으로 사용을 할 떄에는 좀 더 주의해야 한다.

항목 4: 연역된 형식을 파악하는 방법을 알아두라

IDE나 std::type_info::name이 항상 완벽하지는 않다. boost::typeindex::type_id_with_cvr은 정확하긴 하지만 내가 잘 알고있는 것이 최고다.

2장 auto

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

초기화 필수, 불필요한 복사 방지, 클로저 저장, 암묵적 변환 방지, 유지보수의 편리함 등의 이유로 auto를 사용하는 것이 좋다.

항목 6: auto가 원치 않은 형식으로 연역될 때에는 명시적 형식의 초기치를 사용하라

std::vector의 [] 접근 같은 대리자 클래스를 사용할 경우엔 static_cast으로 명시적으로 바꿔서 auto에 대입한다.

3장 현대적 C++에 적용하기

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

중괄호 초기화는 좋긴 하지만 std::initializer_list 매개변수가 있는 경우엔 가능하면 항상 이걸 선호하므로 주의가 필요하다. 선택과 스타일의 영역.

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

템플릿에 0이나 NULL을 넘기면 int 타입으로 인식해 문제가 생길 수 있다.

항목 9: typedef보다 별칭 선언을 선호하라

using은 typedef와 동일하게 동작하지만 템플릿을 작성하는 상황에 유리하다.

항목 10: 범위 없는 enum보다 범위 있는 enum을 선호하라

enum class는 전방 선언, 이름 오염, 암묵적 변환 방지 등의 장점이 있다.

항목 11: 정의되지 않은 비공개 함수보다 삭제된 함수를 선호하라

예전에 사용하던 private 선언 후 구현하지 않는 방법은 = delete가 없어서 그랬던 것.

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

재정의 과정에서 실수 하거나 의도와 다르게 구현하는 것을 override와 final로 방지할 수 있다.

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

C++11에서는 const_iterator 사용이 쉬워졌기 때문에 안 쓸 이유가 없다.

항목 14: 예외를 방출하지 않을 함수는 noexcept로 선언하라

이동, swap 등에서 noexcept를 사용하면 컴파일러 최적화 여지가 크다. 단, 한 번 사용하면 없애기 어려우므로 신중해야 한다.

항목 15: 가능하면 항상 constexpr을 사용하라

객체와 함수가 다르게 동작하는 것을 알고, 계속 constexpr을 유지할 수 있다면 사용하는 것이 항상 좋다.

항목 16: const 멤버 함수를 스레드에 안전하게 작성하라

const 멤버 함수에서 의도적으로 mutable 멤버 변수를 조작할 경우 atomic, mutex 등을 이용해 스레드 안전하게 작성해야 한다.

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

소멸자, 복사, 이동 관련 함수 중 하나라도 사용자가 정의하면 이동 생성/대입은 자동 생성되지 않으므로 필요하면 = default를 통해 명시해 주는 것이 성능 하락을 방지할 수 있다.

4장 똑똑한 포인터

항목 18: 소유권 독점 자원의 관리에는 std::unique_ptr를 사용하라

unique_ptr에 소멸 함수를 직접 정의할 수도 있다.

항목 19: 소유권 공유 자원의 관리에는 std::shared_ptr를 사용하라

shared_ptr은 제어 블록이 생성되며, 이 제어 블록이 복사되는 일은 없어야 한다. 이를 위해 std::enable_shared_from_this<> 라는 기반 클래스 탬플릿을 상속받기도 한다.

항목 20: std::shared_ptr처럼 작동하되 대상을 잃을 수도 있는 포인터가 필요하다면 std::weak_ptr를 사용하라

계층이 엄격히 분리되지 않는 구조에서 shared_ptr의 수명 주기에 따라 해당 shared_ptr을 관찰하는 포인터가 필요하다면 weak_ptr이 적합하다 (캐싱, 옵저버 패턴, 상호참조 방지 등).

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

코드, 속도, 메모리 사용량 면에서 효율적이다. 단, 커스텀 삭제자를 지정하거나 T의 크기가 매우 커서 weak_ptr의 사용이 끝나기까지 메모리를 붙잡고 있는게 싫은 경우엔 new를 사용하는게 더 합당할 수 있다.

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

unique_ptr을 사용할 경우엔 삭제자가 정의에 포함되기 때문에, 컴파일 시점에서 완전한 클래스여야 한다. 따라서 cpp 정의부로 특수 멤버 함수들의 구현을 옮겨 컴파일 에러를 피해야 한다.

5장 오른값 참조, 이동 의미론, 완벽 전달

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

move는 항상 오른값 캐스팅을, forward는 오른 값일 때만 오른 값 캐스팅을 수행한다.

항목 24: 보편 참조와 오른값 참조를 구별하라

형식 연역이 사용되는 곳(T&&, auto&&)에서 && 표시는 보편 참조를 뜻한다. 그 외에는 오른값 참조.

항목 25: 오른값 참조에는 std::move를, 보편 참조에는 std::forward를 사용하라

제곧내. 단, RVO의 대상이 될 수 있는 곳에 std::move를 사용하는 것은 나쁘다.

항목 26: 보편 참조에 대한 중복적재를 피하라

중복 적재를 할 경우 컴파일러가 생각하는 우선순위가 사람과 달라질 여지가 크다. 보편 참조가 다 잡아먹는다.

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

몇 가지 기법들을 소개하나 결국 최종 방법은 매우 복잡해져 사용시 재숙지가 필요학겠다.

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

하나라도 왼 값이 있으면 왼 값으로, 아니면 오른 값으로 축약된다.

항목 29: 이동 연산이 존재하지 않고, 적용되지 않는다고 가정하라

탬플릿같은 경우 이동 연산이 없다고 생각하고 보수적으로 만들 필요도 있다.

항목 30: 완벽 전달이 실패하는 경우들을 잘 알아두라

형태가 모호한 값은 완벽 전달하지 못하는 경우가 있다.

6장 람다 표현식

항목 31: 기본 갈무리 모드를 피하라

갈무리 대상이 명확히 보이지 않아 실수하기 쉽다. 특히 기본 값 갈무리는 람다가 자기 완결 적이라는 착각을 하게 만든다.

항목 32: 객체를 클로저 안으로 이동하려면 초기화 갈무리를 사용하라

C++ 11은 초기화 갈무리를 지원하지 않기 떄문에 std::bind를 사용해서 우회하는 방법을 사용할 수 있다.

항목 33: std::forward를 통해서 전달할 auto&& 매개변수에는 decltype을 사용하라

제곧내. decltype이 완벽히 타입을 유추할 수 있다.

항목 34: std::bind보다 람다를 선호하라

가독성이 더 뛰어나고, 복사/이동 동작이 코드에 드러나며 컴파일러 인라인화를 통해 성능도 향상될 여지가 있다.

7장 동시성 API

항목 35: 스레드 기반 프로그래밍보다 과제 기반 프로그래밍을 선호하라

직접 스레드를 다루는 것에 들어가는 구현 비용을 C++ 라이브러리 구현자들에게 위임할 수 있다. 단, 네이티브 핸들을 사용해 스레드 우선선위 지정 등의 일을 해야 하는 경우 직접 스레드 기반 프로그래밍을 해야 하는 경우도 있다.

항목 36: 비동기성이 필수일 때에는 std::launch::async를 지정하라

std::async의 기본 사용은 비동기/동기 실행을 모두 허용하기 때문. 비동기로 실행될 경우에 대한 예외 등을 철저히 작성하거나 강제로 비동기로 실행할 수 있다.

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

합류 가능한 상태로 스레드가 종료한다면 프로그램이 종료된다. RAII 방식을 이용해 소멸자에서 합류 가능한 상태이면 join/detach 해주도록 만들 수 있다. 이 경우 std::thread 객체는 마지막에 선언하는게 안전하다.

항목 38: 스레드 핸들 소멸자들의 다양한 행동 방식을 주의하라

std::async로 생성한 std::future는 자신이 공유 상태를 참조하는 마지막 미래 객체라면 과제가 완료될 때까지 소멸자가 불리지 않는다. (암묵적 join 호출)

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

std::promise와 std::future(std::shared_future)를 사용하면 뮤택스나 스레드 낭비 없이 단발성 사건 알림을 구현할 수 있다.

항목 40: 동시성에는 std::atomic을 사용하고, volatile은 특별한 메모리에 사용하라

둘은 용도가 전혀 다르다. volatile의 경우 컴파일러가 임의로 메모리에 대한 읽기/쓰기를 최적화 하지 않도록 강제한다. 메모리에 값을 쓰는 것 자체가 통신 등의 목적을 가지고 있는 특수한 경우에 사용할 수 있다.

8장 다듬기

항목 41: 이동이 저렴하고 항상 복사되는 복사 가능 매개변수에 대해서는 값 전달을 고려하라

이동 생성이 가능해졌기 때문에 이동 비용이 저렴하다면 값 전달도 고려해봄직 하다. 구현이 훨씬 간단하다.

항목 42: 삽입 대신 생성 삽입을 고려하라

이론적으로 생성 삽입의 속도가 더 떨어질 일은 없다.

profile
개발새발
post-custom-banner

0개의 댓글