개인 생성자가 개인 생성자가 아닌 경우는 언제입니까?


92

유형이 있고 기본 생성자를 비공개로 만들고 싶다고 가정 해 보겠습니다. 나는 다음과 같이 씁니다.

class C {
    C() = default;
};

int main() {
    C c;           // error: C::C() is private within this context (g++)
                   // error: calling a private constructor of class 'C' (clang++)
                   // error C2248: 'C::C' cannot access private member declared in class 'C' (MSVC)
    auto c2 = C(); // error: as above
}

큰.

그러나 생성자는 내가 생각한 것처럼 비공개가 아닌 것으로 밝혀졌습니다.

class C {
    C() = default;
};

int main() {
    C c{};         // OK on all compilers
    auto c2 = C{}; // OK on all compilers
}    

이것은 나를 매우 놀랍고, 예상치 못한, 명백히 원하지 않는 행동이라고 생각합니다. 왜 괜찮습니까?


25
아닌가 C c{};생성자가 호출되지 않도록 집계 초기화?
NathanOliver 2016-06-03

5
@NathanOliver가 말한 것. 사용자 제공 생성자가 없으므로 C집계도 마찬가지 입니다.
Kerrek SB

5
@KerrekSB 동시에, 사용자가 ctor를 명시 적으로 선언한다고해서 사용자가 제공 한 ctor를 만들지 않는다는 것이 저에게 상당히 놀랐습니다.
Angew는 더 이상 SO

1
우리가 여기있는 이유 : 그의를 @Angew
배리

2
@Angew 만약 그것이 공개 배우라면 =default더 합리적으로 보일 것입니다. 그러나 개인 =default배우는 무시해서는 안되는 중요한 것 같습니다. 더욱이, class C { C(); } inline C::C()=default;상당히 다른 것은 다소 놀랍습니다.
Yakk-Adam Nevraumont 2016 년

답변:


61

트릭은 C ++ 14 8.4.2 / 5 [dcl.fct.def.default]에 있습니다.

... 함수는 사용자 가 선언하고 첫 번째 선언에서 명시 적으로 기본값이 설정되거나 삭제되지 않은 경우 사용자가 제공 합니다. ...

즉, C의 기본 생성자는 첫 번째 선언에서 명시 적으로 기본값이 지정 되었기 때문에 실제로 사용자가 제공 하지 않습니다 . 따라서 C사용자가 제공 한 생성자가 없으므로 8.5.1 / 1 [dcl.init.aggr]에 따라 집계됩니다.

집합체 배열 또는 클래스 (9 절)에는 사용자가 제공 생성자와 (12.1), 개인 작업 또는 보호 비 정적 데이터 멤버 (11.)없이 기본 클래스 (10 절), 어떤 가상 함수 (10.3이고 ).


13
실제로, 작은 표준 결함 : 기본 ctor가 비공개라는 사실은이 컨텍스트에서 사실상 무시됩니다.
Yakk-Adam Nevraumont 2016 년

2
@Yakk 나는 그것을 판단 할 자격이 없다고 생각합니다. 하지만 사용자가 제공하지 않는 배우에 대한 문구는 매우 신중하게 보입니다.
Angew는 더 이상 SO

1
@Yakk : 음, 예, 아니오. 클래스에 데이터 멤버가있는 경우이를 비공개로 만들 수 있습니다. 데이터 구성원이 없으면이 상황이 누구에게나 심각한 영향을 미치는 상황은 거의 없습니다.
Kerrek SB

2
@KerrekSB 클래스의 객체를 생성 할 수있는 사람에 따라 누가 함수를 호출 할 수 있는지 제어하는 ​​일종의 "액세스 토큰"을 사용하려는 경우 중요합니다.
Angew는 더 이상 SO

5
@Yakk 더 흥미로운 점은 C{}생성자가 deleted 인 경우에도 작동 한다는 것 입니다.
Barry

56

기본 생성자를 호출하는 것이 아니라 집계 유형에서 집계 초기화를 사용하고 있습니다. 집계 유형은 처음 선언 된 위치에 기본 설정되어있는 한 기본 생성자를 가질 수 있습니다.

가입일 [dcl.init.aggr] / 1 :

집합체는 다음과 같은 배열 또는 클래스 (Clause [class])입니다.

  • 사용자 제공 생성자 없음 ([class.ctor]) (기본 클래스에서 상속 된 생성자 ([namespace.udecl]) 포함),
  • 비공개 또는 보호 된 비 정적 데이터 멤버 없음 (Clause [class.access]),
  • 가상 기능 없음 ([class.virtual]) 및
  • 가상, 개인 또는 보호 된 기본 클래스 ([class.mi])가 없습니다.

[dcl.fct.def.default] / 5부터

명시 적으로 기본값이 지정된 함수와 암시 적으로 선언 된 함수를 집합 적으로 기본값이 지정된 함수라고하며 구현시 해당 함수에 대한 암시 적 정의 ([class.ctor] [class.dtor], [class.copy])를 제공해야합니다. 이는 삭제 된 것으로 정의하는 것을 의미 할 수 있습니다. . 함수는 사용자가 선언하고 명시 적으로 기본값이 설정되지 않았거나 첫 번째 선언에서 삭제되지 않은 경우 사용자가 제공합니다. 사용자가 제공 한 명시 적으로 기본값이 지정된 함수 (즉, 첫 번째 선언 이후 명시 적으로 기본값이 지정됨)는 명시 적으로 기본값이 설정된 지점에서 정의됩니다. 그러한 함수가 암시 적으로 삭제 된 것으로 정의되면 프로그램은 잘못된 것입니다.[참고 : 함수를 첫 번째 선언 이후 기본값으로 선언하면 효율적인 실행과 간결한 정의를 제공하는 동시에 진화하는 코드베이스에 대한 안정적인 바이너리 인터페이스를 사용할 수 있습니다. — 끝 참고]

따라서 집합체에 대한 요구 사항은 다음과 같습니다.

  • 비공개 회원 없음
  • 가상 기능 없음
  • 가상 또는 비공개 기본 클래스 없음
  • 상속 된 사용자 제공 생성자가 없거나 다음과 같은 생성자 만 허용합니다.
    • 암시 적으로 선언되거나
    • 명시 적으로 선언되고 동시에 기본값으로 정의됩니다.

C 이러한 모든 요구 사항을 충족합니다.

당연히 빈 기본 생성자를 제공하거나 선언 한 후 생성자를 기본값으로 정의하여이 잘못된 기본 생성 동작을 제거 할 수 있습니다.

class C {
    C(){}
};
// --or--
class C {
    C();
};
inline C::C() = default;

2
나는이 대답이 Angew의 대답보다 다소 낫지 만, 시작 부분에서 최대 두 문장으로 요약하면 도움이 될 것이라고 생각합니다.
PJTraill

7

AngewjaggedSpire의 답변은 훌륭하고. 과. 과.

그러나 , 상황이 약간 변경되고 OP의 예제가 더 이상 컴파일되지 않습니다.

class C {
    C() = default;
};

C p;          // always error
auto q = C(); // always error
C r{};        // ok on C++11 thru C++17, error on C++20
auto s = C{}; // ok on C++11 thru C++17, error on C++20

두 답변에서 지적했듯이 후자의 두 선언이 작동하는 이유 C는 집계 이기 때문이며 집계 초기화입니다. 그러나 P1008 (OP와 너무 다르지 않은 동기 부여 예제 사용) 의 결과 로 C ++ 20에서 집계 정의가 [dcl.init.aggr] / 1 에서 변경되었습니다 .

집계는 다음과 같은 배열 또는 클래스 ([class])입니다.

  • 아니오 사용자 선언 또는 상속 생성자 ([class.ctor),
  • 비공개 또는 보호 된 직접 비 정적 데이터 멤버 없음 ([class.access]),
  • 가상 기능 없음 ([class.virtual]) 및
  • 가상, 개인 또는 보호 된 기본 클래스 ([class.mi])가 없습니다.

내 강조. 이제 요구 사항은 사용자 선언 생성자가 아니지만 이전에는 두 사용자가 답변을 인용하고 C ++ 11 , C ++ 14C ++ 17에 대해 역사적으로 볼 수 있기 때문에 사용자가 제공 한 생성자가 없습니다. . 의 기본 생성자 C는 사용자가 선언하지만 사용자가 제공하지 않으므로 C ++ 20에서 집계가 중단됩니다.


다음은 집계 변경의 또 다른 예시입니다.

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

B기본 클래스가 있기 때문에 C ++ 11 또는 C ++ 14에서는 집계가 아닙니다. 결과적으로 의 보호 된 기본 생성자에 B{}액세스 할 수있는 기본 생성자 (사용자가 선언했지만 사용자가 제공하지 않음 ) 만 호출합니다 A.

C ++ 17에서는 P0017 의 결과로 기본 클래스를 허용하도록 집계가 확장되었습니다. BC ++ 17의 집계입니다. 즉 B{}, 하위 개체를 포함한 모든 하위 개체를 초기화해야하는 집계 초기화입니다 A. 그러나 A의 기본 생성자가 보호되어 있기 때문에 액세스 할 수 없으므로이 초기화의 형식이 잘못되었습니다.

C ++ 20에서는 B의 사용자 선언 생성자로 인해 다시 집계가되지 않으므로 B{}기본 생성자 호출로 되돌아 가며 이는 다시 올바른 형식의 초기화입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.