GN⁺: 생성자 없는 초기화 필요성
(consteval.ca)나는 생성자가 없고 초기화해야 함
-
서론
- 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 전면 패널이 있음