C ++에서 const 객체를 기본 생성하기 위해 사용자 제공 기본 생성자가 필요한 이유는 무엇입니까?


99

C ++ 표준 (섹션 8.5)은 다음과 같이 말합니다.

프로그램이 const 한정 형식 T의 개체에 대한 기본 초기화를 호출하는 경우 T는 사용자가 제공 한 기본 생성자가있는 클래스 형식이어야합니다.

왜? 이 경우 사용자 제공 생성자가 필요한 이유를 생각할 수 없습니다.

struct B{
  B():x(42){}
  int doSomeStuff() const{return x;}
  int x;
};

struct A{
  A(){}//other than "because the standard says so", why is this line required?

  B b;//not required for this example, just to illustrate
      //how this situation isn't totally useless
};

int main(){
  const A a;
}

2
이 행은 선언했지만 정의 / 초기화하지 않았기 때문에 예제에서 필요하지 않은 것 같습니다 ( ideone.com/qqiXR 참조 ) a.하지만 gcc-4.3.4는이를 허용하는 경우에도 허용합니다 ( ideone.com/uHvFS 참조 ).
Ray Toal 2011 년

위의 예는 선언하고 정의합니다 a. Comeau는 라인이 주석 처리 된 경우 "const 변수"a "에 이니셜 라이저가 필요합니다-클래스"A "에는 명시 적으로 선언 된 기본 생성자가 없습니다"라는 오류가 발생합니다.
Karu 2011 년


4
이것은 C ++ 11, 당신이 쓸 수있는 고정됩니다 const A a{}:)
하워드 LOVATT

답변:


10

이것은 (표준의 모든 버전에 대한) 결함으로 간주되었으며 CWG (Core Working Group) Defect 253에 의해 해결되었습니다 . http://eel.is/c++draft/dcl.init#7 의 표준 상태에 대한 새로운 문구

T의 기본 초기화가 T의 사용자 제공 생성자를 호출하거나 (기본 클래스에서 상속되지 않음) 클래스 유형 T는 const-default-constructible입니다.

  • T의 각 직접 비 변형 비 정적 데이터 멤버 M은 기본 멤버 이니셜 라이저를 갖거나 M이 클래스 유형 X (또는 그 배열) 인 경우 X는 const-default-constructible입니다.
  • T가 하나 이상의 비 정적 데이터 멤버가있는 공용체 인 경우 정확히 하나의 변형 멤버에 기본 멤버 이니셜 라이저가 있습니다.
  • T가 공용체가 아닌 경우 하나 이상의 비 정적 데이터 멤버 (있는 경우)가있는 각 익명 공용체 멤버에 대해 정확히 하나의 비 정적 데이터 멤버에 기본 멤버 이니셜 라이저가 있습니다.
  • 잠재적으로 생성 된 각 기본 클래스 T는 const-default-constructible입니다.

프로그램이 const-qualified 유형 T의 객체의 기본 초기화를 요구하는 경우, T는 const-default-constructible 클래스 유형 또는 그 배열이어야합니다.

이 표현은 본질적으로 명백한 코드가 작동 함을 의미합니다. 모든베이스와 멤버를 초기화 A const a;하면 생성자의 철자 나 철자에 관계없이 말할 수 있습니다 .

struct A {
};
A const a;

gcc는 4.6.4부터 이것을 받아 들였습니다. clang은 3.9.0부터 이것을 받아 들였습니다. Visual Studio도이를 허용합니다 (적어도 2017 년에는 확실하지 않음).


3
그러나 이것은 struct A { int n; A() = default; }; const A a;허용하는 동안 여전히 금지됩니다. struct B { int n; B() {} }; const B b;왜냐하면 새로운 문구는 여전히 "사용자가 선언 한"이 아닌 "사용자가 제공 한"이라고 말하고위원회가 명시 적으로 기본값 인 기본 생성자를이 DR에서 제외하기로 선택한 이유를 긁적입니다. 초기화되지 않은 멤버가있는 const 객체를 원한다면 클래스는 중요하지 않습니다.
Oktalist

1
흥미롭지 만, 내가 만난 엣지 케이스가 여전히 있습니다. 로 MyPOD포드가되는 struct, static MyPOD x;- 제로 초기화에 의존하면 멤버 변수 (들)을 적절하게 설정하는 (? 오른쪽에 하나가 있다는 것입니다) - 컴파일을하지만, static const MyPOD x;하지 않습니다. 것을 어떤 기회가 있습니까 고정 얻을 것이다는?
Joshua Green

66

그 이유는 클래스에 사용자 정의 생성자가 없으면 POD가 될 수 있고 POD 클래스는 기본적으로 초기화되지 않기 때문입니다. 따라서 초기화되지 않은 POD의 const 객체를 선언하면 어떻게 사용됩니까? 그래서 저는 표준이이 규칙을 시행하여 객체가 실제로 유용 할 수 있도록한다고 생각합니다.

struct POD
{
  int i;
};

POD p1; //uninitialized - but don't worry we can assign some value later on!
p1.i = 10; //assign some value later on!

POD p2 = POD(); //initialized

const POD p3 = POD(); //initialized 

const POD p4; //uninitialized  - error - as we cannot change it later on!

하지만 수업을 비 -POD로 만드는 경우 :

struct nonPOD_A
{
    nonPOD_A() {} //this makes non-POD
};

nonPOD_A a1; //initialized 
const nonPOD_A a2; //initialized 

POD와 비 -POD의 차이점에 유의하십시오.

사용자 정의 생성자는 클래스를 POD가 아닌 것으로 만드는 한 가지 방법입니다. 이를 수행 할 수있는 몇 가지 방법이 있습니다.

struct nonPOD_B
{
    virtual void f() {} //virtual function make it non-POD
};

nonPOD_B b1; //initialized 
const nonPOD_B b2; //initialized 

nonPOD_B는 사용자 정의 생성자를 정의하지 않습니다. 그것을 컴파일하십시오. 다음과 같이 컴파일됩니다.

그리고 가상 기능에 주석을 달면 예상대로 오류가 발생합니다.


글쎄, 당신은 그 구절을 오해했다고 생각합니다. 먼저 다음과 같이 말합니다 (§8.5 / 9).

객체에 대해 초기화 프로그램이 지정되지 않고 객체가 (가능하면 cv-qualified) non-POD 클래스 유형 (또는 배열) 인 경우 객체는 기본값으로 초기화됩니다. [...]

cv-qualified 유형일 수있는 비 POD 클래스에 대해 이야기 합니다. 즉, 초기화 프로그램이 지정되지 않은 경우 POD가 아닌 개체는 기본적으로 초기화됩니다. 그리고 기본 초기화는 무엇입니까? 비 POD의 경우 사양에 (§8.5 / 5),

T 유형의 객체를 기본 초기화한다는 것은 다음을 의미합니다.
— T가 비 POD 클래스 유형 (9 절)이면 T에 대한 기본 생성자가 호출됩니다 (T에 액세스 할 수있는 기본 생성자가 없으면 초기화 형식이 잘못됨).

사용자 정의 또는 컴파일러 생성 여부에 관계없이 T의 기본 생성자 에 대해 이야기 합니다.

이것에 대해 확실하다면 다음에 나오는 사양 ((§8.5 / 9),

[...]; 객체가 const 한정 유형이면 기본 클래스 유형은 사용자가 선언 한 기본 생성자를 가져야합니다.

따라서이 텍스트 는 객체가 const-qualified POD 유형이고 초기화 프로그램이 지정되지 않은 경우 프로그램의 형식이 잘못됨을 의미 합니다 (POD가 기본적으로 초기화되지 않기 때문에).

POD p1; //uninitialized - can be useful - hence allowed
const POD p2; //uninitialized - never useful  - hence not allowed - error

그건 그렇고, 이것은 POD가 아니기 때문에 잘 컴파일 되고 default-initialized 될 수 있습니다 .


1
마지막 예제는 컴파일 오류라고 생각합니다 nonPOD_B. 사용자가 제공 한 기본 생성자가 없으므로 줄 const nonPOD_B b2이 허용되지 않습니다.
Karu

1
클래스를 비 POD로 만드는 또 다른 방법은 POD가 아닌 데이터 멤버를 제공하는 것입니다 (예 : B질문의 내 구조체 ). 그러나이 경우에도 사용자가 제공 한 기본 생성자가 필요합니다.
Karu

"프로그램이 const 한정 유형 T의 객체에 대한 기본 초기화를 호출하면 T는 사용자가 제공 한 기본 생성자가있는 클래스 유형이됩니다."
Karu

@Karu : 나는 그것을 읽었다. 사양에 다른 구절이있는 것 같습니다 const. 컴파일러에서 생성 한 default-constructor를 호출하여 POD가 아닌 개체를 초기화 할 수 있습니다.
Nawaz 2011 년

2
ideone 링크가 끊어진 것 같습니다. §8.5가 ​​POD를 전혀 언급하지 않기 때문에이 답변을 C ++ 11 / 14로 업데이트 할 수 있다면 좋을 것입니다.
Oktalist

12

내 입장에서는 순수한 추측이지만 다른 유형에도 비슷한 제한이 있다는 것을 고려하십시오.

int main()
{
    const int i; // invalid
}

따라서이 규칙은 일관성이있을뿐만 아니라 (재귀 적으로) 단일화 const(하위) 객체를 방지 합니다.

struct X {
    int j;
};
struct A {
    int i;
    X x;
}

int main()
{
    const A a; // a.i and a.x.j in unitialized states!
}

질문의 다른 측면 (기본 생성자를 가진 유형에 대해 허용)에 관해서는 사용자가 제공 한 기본 생성자를 가진 유형이 생성 후 항상 합리적인 상태에 있어야한다는 생각이 있습니다. 규칙은 다음을 허용합니다.

struct A {
    explicit
    A(int i): initialized(true), i(i) {} // valued constructor

    A(): initialized(false) {}

    bool initialized;
    int i;
};

const A a; // class invariant set up for the object
           // yet we didn't pay the cost of initializing a.i

그런 다음 '적어도 하나의 구성원은 사용자가 제공 한 기본 생성자에서 현명하게 초기화되어야합니다'와 같은 규칙을 공식화 할 수 있지만 이는 Murphy로부터 보호하는 데 너무 많은 시간을 소비하는 방식입니다. C ++는 특정 지점에서 프로그래머를 신뢰하는 경향이 있습니다.


그러나를 추가 A(){}하면 오류가 사라 지므로 아무것도 방지하지 않습니다. 규칙은 재귀 적으로 작동하지 않습니다 X(){}. 해당 예제에는 필요 하지 않습니다 .
Karu

2
글쎄요, 적어도 프로그래머에게 생성자를 추가하도록 강요함으로써 그는 문제에 대해 잠깐 생각을하게되었고 아마도 사소하지 않은 문제를 생각
해낼 수

그 : 고정 - @Karu 나는 절반 문제 해결
뤽 덴톤

4
@arne : 유일한 문제는 잘못된 프로그래머라는 것입니다. 클래스를 인스턴스화하려는 사람은 문제에 대해 원하는 모든 생각을 할 수 있지만 클래스를 수정하지 못할 수 있습니다. 클래스 작성자는 멤버에 대해 생각하고 모두 암시 적 기본 생성자에 의해 합리적으로 초기화되었으므로 멤버를 추가하지 않았습니다.
Karu

3
내가 표준의이 부분에서 가져온 것은 "언젠가 누군가가 const 인스턴스를 만들고 싶어하는 경우를 대비하여 항상 항상 비 POD 유형에 대한 기본 생성자를 선언한다"는 것입니다. 약간 과잉 인 것 같습니다.
Karu 2011 년

3

저는 Meeting C ++ 2018에서 Timur Doumler의 강연을보고 있었고 마침내 표준에 사용자가 선언 한 생성자가 아닌 사용자 제공 생성자가 필요한 이유를 마침내 깨달았습니다. 가치 초기화 규칙과 관련이 있습니다.

다음 두 가지 클래스를 고려하십시오 A. 사용자가 선언 한 생성자 B가 있고 사용자가 제공 한 생성자가 있습니다.

struct A {
    int x;
    A() = default;
};
struct B {
    int x;
    B() {}
};

언뜻보기에이 두 생성자가 동일하게 작동한다고 생각할 수 있습니다. 하지만 기본 초기화 만 동일하게 작동하는 반면 값 초기화가 어떻게 다르게 작동하는지 확인하십시오.

  • A a;기본 초기화 : 멤버 int x가 초기화되지 않았습니다.
  • B b;기본 초기화 : 멤버 int x가 초기화되지 않았습니다.
  • A a{};값 초기화 : 멤버 int x0으로 초기화 됩니다.
  • B b{};값 초기화 : 멤버 int x가 초기화되지 않았습니다.

이제 const다음 을 추가하면 어떤 일이 발생하는지 확인하십시오 .

  • const A a;기본 초기화 : 질문에 인용 된 규칙으로 인해 형식 이 잘못 되었습니다.
  • const B b;기본 초기화 : 멤버 int x가 초기화되지 않았습니다.
  • const A a{};값 초기화 : 멤버 int x0으로 초기화 됩니다.
  • const B b{};값 초기화 : 멤버 int x가 초기화되지 않았습니다.

초기화되지 않은 const스칼라 (예 : int x멤버)는 쓸모가 없습니다. 쓰기는 형식이 잘못되었으며 (이기 때문에 const) 읽기는 UB입니다 (불확정 한 값을 보유하기 때문에). 따라서이 규칙 은 사용자가 제공 한 생성자 추가하여 이니셜 라이저 추가 하거나 위험한 동작에 옵트 인하도록 강제하여 그러한 것을 생성하지 못하게합니다 .

[[uninitialized]]의도적으로 객체를 초기화하지 않을 때 컴파일러에게 알리는 것과 같은 속성을 갖는 것이 좋을 것이라고 생각 합니다. 그러면 우리는이 코너 케이스를 피하기 위해 우리 클래스를 사소한 기본 구조가 아닌 것으로 만들도록 강요받지 않을 것입니다. 이 속성은 실제로 제안 되었지만 다른 모든 표준 속성과 마찬가지로 표준 동작을 요구하지 않으며 컴파일러에 대한 힌트 일뿐입니다.


1

축하합니다 const. 이니셜 라이저가없는 선언에 대해 사용자 정의 생성자가 필요하지 않은 경우 를 만들었습니다.

이제 귀하의 사건을 다루면서도 여전히 불법이어야하는 사건을 불법으로 만드는 규칙을 합리적으로 재조명 할 수 있습니까? 5 개 또는 6 개 문단 미만입니까? 어떤 상황에서도 어떻게 적용해야하는지 쉽고 분명합니까?

나는 당신이 만든 선언이 의미가 있도록 허용하는 규칙을 만드는 것은 정말 어렵고, 코드를 읽을 때 사람들이 이해할 수있는 방식으로 규칙을 적용 할 수 있는지 확인하는 것이 더 어렵다고 가정합니다. 이해하고 적용하기 어려운 매우 미묘하고 복잡한 규칙보다 대부분의 경우에 올바른 일이었던 다소 제한적인 규칙을 선호합니다.

문제는 규칙이 더 복잡해야하는 설득력있는 이유가 있습니까? 규칙이 더 복잡하면 훨씬 더 간단하게 작성할 수있는 코드를 작성하거나 이해하기가 매우 어렵습니까?


1
제가 제안하는 표현은 다음과 같습니다. "프로그램이 const 규정 유형 T의 객체에 대한 기본 초기화를 호출하면 T는 비 POD 클래스 유형이됩니다." 이것은 const POD x;불법과 마찬가지로 const int x;불법으로 만들 수 있지만 const NonPOD x;(이것은 POD에 쓸모가 없기 때문에 이치에 맞습니다) 합법적입니다 (유용한 생성자 / 소멸자를 포함하는 하위 객체를 가질 수 있거나 유용한 생성자 / 소멸자 자체를 가질 수 있기 때문에 의미가 있습니다) .
Karu

@Karu-그 문구가 작동 할 수 있습니다. 저는 RFC 표준에 익숙하므로 'T should be'가 'T must be'로 읽어야한다고 생각합니다. 그러나 네, 작동 할 수 있습니다.
Omnifarious 2011-09-17

@Karu-struct NonPod는 어떻습니까 {int i; 가상 무효 f () {}}? const NonPod x를 만드는 것은 이치에 맞지 않습니다. 적법한.
gruzovator

1
@gruzovator 빈 사용자 선언 기본 생성자가 있으면 더 이상 의미가 있습니까? 내 제안은 표준의 무의미한 요구 사항을 제거하려는 것입니다. 그것의 유무에 관계없이 말도 안되는 코드를 작성하는 방법은 무한히 많습니다.
Karu 2013 년

1
@Karu 동의합니다. 표준의이 규칙 때문에 사용자 정의 생성자 를 가져야하는 많은 클래스가 있습니다 . 나는 gcc 행동을 좋아한다. 그것은 예를 들어, 수 struct NonPod { std::string s; }; const NonPod x;그리고 NonPod이 때 오류를 제공합니다struct NonPod { int i; std::string s; }; const NonPod x;
gruzovator
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.