1P by neo 2달전 | favorite | 댓글 1개

나는 생성자가 없고 초기화해야 함

  • 서론

    • C++를 처음 배울 때, 컴파일러가 기본 생성자를 제공하는 경우에 대해 배웠음.
    • 특정 상황에서 객체가 초기화되지 않을 수 있는 위험성에 대해 고민하게 됨.
  • 기본 초기화와 값 초기화

    • T t;는 기본 초기화를 수행함.
      • T가 클래스 타입이고 기본 생성자가 있으면 실행됨.
      • T가 배열 타입이면 각 요소를 기본 초기화함.
      • 그렇지 않으면 아무것도 하지 않음.
    • T t{};는 값 초기화를 수행함.
      • T가 클래스 타입이면 기본 생성자가 없거나 사용자 제공 또는 삭제된 기본 생성자가 있으면 기본 초기화함.
      • 그렇지 않으면 0으로 초기화한 후 기본 초기화함.
      • T가 배열 타입이면 각 요소를 값 초기화함.
      • 그렇지 않으면 0으로 초기화함.
  • 기본 생성자

    • 기본 생성자를 선언하지 않으면 컴파일러가 암시적으로 기본 생성자를 선언함.
    • 암시적으로 선언된 기본 생성자는 빈 본문과 빈 멤버 초기화 목록을 가짐.
    • 예시:
      struct T {
        int x;
        T() = default;
      };
      T t{};
      std::cout << t.x << std::endl; // 출력 결과는 0
      
  • 암시적으로 정의된 기본 생성자

    • 기본 생성자가 암시적으로 선언되거나 명시적으로 기본값으로 선언되면 컴파일러가 암시적으로 정의된 기본 생성자를 제공함.
    • 예시:
      struct T {
        T();
      };
      T::T() = default;
      T t{};
      std::cout << t.x << std::endl; // 출력 결과는 쓰레기 값
      
  • 기본 생성자를 제공할 수 없는 경우

    • T가 비정적 참조 멤버를 가지고 있는 경우
    • T가 기본 생성할 수 없거나 소멸할 수 없는 비정적 멤버 또는 비추상 기본 클래스를 가지고 있는 경우
    • T가 기본 멤버 초기화자가 없는 const 비정적 멤버를 가지고 있는 경우
  • 올바른 초기화

    • T t{};는 리스트 초기화를 수행함.
    • 리스트 초기화는 직접 리스트 초기화와 복사 리스트 초기화로 나뉨.
    • 예시:
      struct S {
        int a;
        float b;
        char c;
      };
      S s{3, 4.0f, 'S'}; // 생성자 호출 없음
      
  • 리스트 초기화와 집합 초기화

    • 집합 초기화는 리스트 초기화의 특별한 형태로, 클래스 또는 배열의 각 요소를 초기화 목록의 각 요소로 복사 초기화함.
    • 예시:
      struct A {
        const int x;
      };
      A a{}; // a.x는 0으로 초기화됨
      
  • 괄호를 사용한 초기화

    • 괄호를 사용한 초기화는 직접 비리스트 초기화를 수행함.
    • 예시:
      struct T {
        const int& r;
      };
      T t(42); // t.r은 42를 가리키는 참조
      
  • 요약

    • 초기화 규칙은 복잡하지만, 직접 생성자를 작성하면 대부분의 문제를 피할 수 있음.
    • 컴파일러에게 맡기지 말고, 직접 생성자를 작성하는 것이 좋음.

GN⁺의 의견

  • 이 글은 C++ 초기화 규칙의 복잡성을 잘 설명하고 있음.
  • C++의 초기화 규칙을 이해하는 것은 중요한데, 이는 코드의 안정성과 성능에 큰 영향을 미침.
  • 직접 생성자를 작성하는 것이 초기화 문제를 피하는 가장 좋은 방법임.
  • 비슷한 기능을 가진 다른 언어로는 Rust가 있으며, Rust는 초기화 규칙이 더 명확함.
  • 새로운 기술을 채택할 때는 초기화 규칙과 같은 세부 사항을 잘 이해하고 사용하는 것이 중요함.
Hacker News 의견
  • t의 초기화 결과는 0이 될 것임

    • 이는 t가 값 초기화되고, T가 사용자 정의 기본 생성자가 없기 때문에 객체가 0으로 초기화된 후 기본 생성자가 호출되기 때문임
  • 기본 생성자는 멤버를 기본 초기화하며, 값 초기화와는 다름

  • GCC는 이에 동의하는 것 같음

  • 작성자가 실제로 x를 값 초기화하고 있다는 것을 놓쳤음

    • 결과는 기대와 다르게 나옴
  • 규칙의 세부 사항은 복잡하고 때로는 비합리적인 부분이 있음

    • 그러나 대부분의 경우 기대한 결과를 얻을 수 있음
  • 기본 초기화를 명시적으로 만드는 것이 큰 개선점이 될 것임

    • 값 초기화가 일반적이기 때문에 기본 초기화를 원할 때 주석을 작성해야 함
    • "std::array<int, 100> = void;"와 같은 구문이 더 나을 것임
  • 리스트 초기화와 집합 초기화 사이의 연결고리는 리스트 초기화가 집합에 대해 수행될 때 집합 초기화가 수행됨

    • 단, 리스트에 하나의 인수만 있는 경우 직접 초기화가 수행됨
  • 한 요소의 경우는 두 개 이상의 요소와 다르게 작동함

    • 이는 매개변수 팩에서 구조체를 생성하는 것이 점점 더 간단해지는 언어에서 발생함
  • 자신의 생성자를 작성할 수 있으며, 하나의 요소만 제공된 튜플이나 배열을 초기화할 수 있음

    • 그러나 특수한 경우 잘못된 생성자가 호출될 수 있음
  • C++11 초기화 리스트가 처음 나왔을 때 이를 발견하고 미쳤다고 생각했음

  • "I Have No Mouth, and I Must Scream" (1967) 언급

  • T::T() = default; 구문 사용

  • 출력 결과가 0이 될 것이라고 기대하지만 실제로는 쓰레기 값이 나올 것임

    • 일부 것들은 완벽할 수 없음
  • 라이브러리 소비자가 라이브러리의 동작을 변경할 수 있게 함

  • 더 많은 C++의 복잡함을 원한다면 C++ FQA를 추천함

    • 15년이 지났지만 C++는 오래된 기능이나 동작을 거의 제거하지 않기 때문에 여전히 유효함
  • 블로그 테마가 DEC 시대의 컴퓨터에서 영감을 받았지만 깔끔하고 미니멀함

    • 신선함
  • 이 내용을 읽으면 어지러움을 느끼게 됨

    • Java 생성자와 객체 초기화를 이해하려고 했던 기억이 남
  • Go와 Rust는 특별한 생성자가 없어서 많은 부분이 단순해짐

    • 생성자를 사용하지 않게 된 후 생성자를 그리워한 경험이 있는지 궁금함
  • 모든 암묵적인 동작을 보여주는 C++ 도구가 있는지 궁금함

    • 예를 들어 추가된 모든 생성자, 암묵적 복사 생성자 등
  • 클래스에 대해 잘못된 정보를 제공함

    • 생성자를 선언한 경우 기본 생성자를 제공하지 않으며 기본 초기화는 컴파일러 진단과 함께 실패함
  • "T t;"는 "아무것도 하지 않는다"는 주장은 잘못됨

    • 예시 코드에서 T t;는 실패함
  • 블로그 헤더에 DEC 전면 패널이 있음