Abseil Tip 88 초기화 방법 =, (), 그리고 {}

title: “Tip of the Week #88: 초기화 방법: =, (), 그리고 {}” layout: tips sidenav: side-nav-tips.html published: true permalink: tips/88 type: markdown order: “088” —

처음 게시: 2015-01-27 (TotW #88)

작성자: Titus Winters (titus@google.com), Google C++ 스타일 심사 위원회를 대표하여

C++11은 “통일된 초기화 문법”이라고 불리는 새로운 문법을 도입했습니다. 이는 다양한 초기화 스타일을 통합하고, 가장 성가신 구문 분석 문제(Most Vexing Parse)를 피하며, 협소 변환(narrowing conversion)을 방지하기 위해 설계되었습니다. 하지만 이 새로운 메커니즘은 또 다른 초기화 문법을 추가했을 뿐이며, 이 역시 고유한 장단점을 가지고 있습니다.


C++11 중괄호 초기화

일부 통일된 초기화 문법 지지자들은 모든 타입의 초기화를 위해 {}와 직접 초기화(= 없이)를 사용할 것을 제안합니다. 대부분의 경우 두 형식 모두 동일한 생성자를 호출합니다:

int x{2};
std::string foo{"Hello World"};
std::vector<int> v{1, 2, 3};

vs.

int x = 2;
std::string foo = "Hello World";
std::vector<int> v = {1, 2, 3};

그러나 이 접근 방식에는 두 가지 단점이 있습니다. 첫째, “통일”이라는 표현은 다소 과장된 면이 있습니다. 무슨 호출이 이루어지고 있는지(일반 독자가 아닌 컴파일러 입장에서) 여전히 애매한 경우가 있습니다.

std::vector<std::string> strings{2}; // 빈 문자열 두 개를 포함한 벡터.
std::vector<int> ints{2};            // 정수 2 하나만 포함한 벡터.

둘째, 이 문법은 직관적이지 않습니다. 다른 일반적인 언어에서는 이와 유사한 문법을 사용하지 않습니다. 기술적인 이유로 인해 특정 상황(특히 제너릭 코드)에서 이 문법이 필요할 수 있다는 점은 인정합니다. 하지만 핵심 질문은: “이 변화를 활용하기 위해 우리의 습관과 기존 코드 이해도를 얼마나 바꿔야 하는가?”입니다. 우리는 통일된 초기화 문법의 장점이 단점보다 크다고 보지 않습니다.


초기화에 대한 모범 사례

따라서 초기화 방법에 대해 다음 가이드를 따르기를 권장합니다. 이 가이드는 코드 작성 시 따르고, 코드 리뷰에서 참고하기에 적합합니다:

  1. 의도된 리터럴 값(예: int, float, std::string 값), 스마트 포인터(std::shared_ptr, std::unique_ptr), 컨테이너(std::vector, std::map 등), 구조체 초기화, 또는 복사 생성에서 초기화할 때는 대입문(=) 구문을 사용하십시오.

    int x = 2;
    std::string foo = "Hello World";
    std::vector<int> v = {1, 2, 3};
    std::unique_ptr<Matrix> matrix = NewMatrix(rows, cols);
    MyStruct x = {true, 5.0};
    MyProto copied_proto = original_proto;
    

    대신 아래와 같은 코드는 피하십시오:

    // 나쁜 코드
    int x{2};
    std::string foo{"Hello World"};
    std::vector<int> v{1, 2, 3};
    std::unique_ptr<Matrix> matrix{NewMatrix(rows, cols)};
    MyStruct x{true, 5.0};
    MyProto copied_proto{original_proto};
    
  2. 적극적인 로직이 수행될 때(단순한 값 조합이 아닌 경우) 전통적인 생성자 문법(괄호)을 사용하십시오.

    Frobber frobber(size, &bazzer_to_duplicate);
    std::vector<double> fifty_pies(50, 3.14);
    

    대신 아래와 같은 코드는 피하십시오:

    // 나쁜 코드
    Frobber frobber{size, &bazzer_to_duplicate}; // 두 개의 매개변수 생성자 또는 초기화 목록 생성자일 수 있음
    std::vector<double> fifty_pies{50, 3.14};   // 두 개의 double 요소를 가진 벡터 생성
    
  3. 위 옵션이 컴파일되지 않는 경우에만 {} 초기화를 사용하십시오.

    class Foo {
     public:
      Foo(int a, int b, int c) : array_{a, b, c} {}
    
     private:
      int array_[5];
      EventManager em{EventManager::Options()}; // 명시적 생성자이기 때문에 {} 필요
    };
    
  4. {} 초기화와 auto를 혼용하지 마십시오.
    예를 들어, 다음 코드는 피하십시오:

    // 나쁜 코드
    auto x{1};
    auto y = {2}; // 이 코드는 std::initializer_list<int>입니다!
    

    (언어 전문가를 위한 추가 설명: 사용할 수 있는 경우 직접 초기화 대신 복사 초기화를 선호하며, 직접 초기화를 사용하는 경우에는 중괄호 대신 괄호를 사용하십시오.)


결론

통일된 초기화 문법의 장점이 일반적으로 단점을 상쇄하지는 않습니다. 이미 컴파일러는 가장 성가신 구문 분석 문제를 경고하고(중괄호 초기화를 사용하거나 괄호를 추가하여 문제를 해결할 수 있음), 협소 변환 방지라는 이점은 중괄호 초기화로 인해 가독성이 떨어지는 것에 비해 큰 장점이 아닙니다. 특히 제너릭 코드에서 중괄호 초기화가 정당화될 수 있는 경우가 있기에, 스타일 가이드에서 이를 엄격히 규정할 필요는 없다고 봅니다.