비 정적 데이터 멤버 및 중첩 클래스 생성자의 클래스 내 초기화를 사용할 때 오류 발생


90

다음 코드는 매우 사소하며 잘 컴파일되어야한다고 예상했습니다.

struct A
{
    struct B
    {
        int i = 0;
    };

    B b;

    A(const B& _b = B())
        : b(_b)
    {}
};

이 코드는 g ++ 버전 4.7.2, 4.8.1, clang ++ 3.2 및 3.3으로 테스트했습니다. 이 코드 ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57770 ) 에 g ++ 4.7.2 segfault가 있다는 사실을 제외 하고 다른 테스트 된 컴파일러는 많이 설명하지 않는 오류 메시지를 제공합니다.

g ++ 4.8.1 :

test.cpp: In constructor ‘constexpr A::B::B()’:
test.cpp:3:12: error: constructor required before non-static data member for ‘A::B::i’ has been parsed
     struct B
            ^
test.cpp: At global scope:
test.cpp:11:23: note: synthesized method ‘constexpr A::B::B()’ first required here 
     A(const B& _b = B())
                       ^

clang ++ 3.2 및 3.3 :

test.cpp:11:21: error: defaulted default constructor of 'B' cannot be used by non-static data member initializer which appears before end of class definition
    A(const B& _b = B())
                    ^

이 코드를 컴파일 가능하게 만드는 것이 가능하며 아무런 차이가 없어야합니다. 두 가지 옵션이 있습니다.

struct B
{
    int i = 0;
    B(){} // using B()=default; works only for clang++
};

또는

struct B
{
    int i;
    B() : i(0) {} // classic c++98 initialization
};

이 코드가 정말 잘못되었거나 컴파일러가 잘못 되었습니까?


3
내 G ++ 4.7.3는 말한다 internal compiler error: Segmentation fault...이 코드에
프레드 푸

2
(오류 C2864 : 'A :: B :: i': 정적 const 정수 데이터 멤버 만 클래스 내에서 초기화 될 수 있음)은 VC2010이 말하는 것입니다. 그 출력은 g ++와 일치합니다. Clang도 말하지만 훨씬 덜 의미가 있습니다. 그렇지 int i = 0않으면 struct에서 변수를 기본값으로 설정할 수 없습니다 static const int i = 0.
Chris Cooper

@Borgleader : BTW 식 B()을 생성자에 대한 함수 호출로 생각하려는 유혹을 피하고 싶습니다 . 당신은 결코 직접 "호출"생성자. 이것을 임시 생성하는 특수 구문으로 생각 B하면 생성자는 다음 메커니즘 내에서 해당 프로세스의 한 부분으로 호출됩니다.
밝기 경주 궤도에

2
흠,에 생성자를 추가하는 것은 B이 작업을 할 것으로 보인다 gcc 4.7.
Shafik Yaghmour

7
흥미롭게도 A의 생성자 정의를 A 밖으로 옮기면 작동하는 것처럼 보입니다 (g ++ 4.7); "기본 기본 생성자는 사용할 수 없습니다 ... 클래스 정의가 끝나기 전에"어떤 차임 소리가납니다.
moonshadow

답변:


84

이 코드가 정말 잘못되었거나 컴파일러가 잘못 되었습니까?

글쎄, 둘 다. 이 표준에는 결함이 있습니다. 즉 ,에 A대한 이니셜 라이저를 구문 분석하는 동안 완료된 것으로 간주 되는 B::i것과 B::B()(이니셜 라이저를 사용하는 B::i)에 대한 정의 내에서 사용할 수 있다고 A합니다. 그것은 분명히 순환 적입니다. 이걸 고려하세요:

struct A {
  struct B {
    int i = (A(), 0);
  };
  A() noexcept(!noexcept(B()));
};

이것은 모순이 있습니다 : B::B()is implicitly noexceptiff A()does not throw, and A()does not throw iff B::B()is not noexcept . 이 영역에는 많은 다른주기와 모순이 있습니다.

이것은 핵심 문제 13601397에 의해 추적됩니다 . 특히 핵심 문제 1397에서 다음 참고 사항을 참고하십시오.

아마도이 문제를 해결하는 가장 좋은 방법은 비 정적 데이터 멤버 이니셜 라이저가 해당 클래스의 기본 생성자를 사용하도록 형식을 잘못 지정하는 것입니다.

이 문제를 해결하기 위해 Clang에서 구현 한 규칙의 특별한 경우입니다. Clang의 규칙은 해당 클래스의 비 정적 데이터 멤버 이니셜 라이저가 구문 분석되기 전에는 클래스의 기본 생성자를 사용할 수 없다는 것입니다. 따라서 Clang은 여기에서 진단을 발행합니다.

    A(const B& _b = B())
                    ^

... Clang은 기본 이니셜 라이저를 구문 분석하기 전에 기본 인수를 구문 분석하고이 기본 인수에는 B의 기본 이니셜 라이저가 이미 구문 분석되어 있어야합니다 (암시 적으로 정의하기 위해 B::B()).


알아 둘만 한. 그러나 생성자가 실제로 "비 정적 데이터 멤버 이니셜 라이저에서 사용"되지 않기 때문에 오류 메시지는 여전히 잘못된 것입니다.
aschepler

이 문제에 대한 특정 과거 경험 때문에 또는 표준 (및 결함 목록)을주의 깊게 읽었 기 때문에 이것을 알고 있습니까? 또한 +1.
옥수숫대

자세한 답변은 +1하세요. 그래서 탈출구는 무엇입니까? 내부 클래스가 완전히 형성 될 때까지 내부 클래스에 의존하는 외부 클래스 멤버의 구문 분석이 지연되는 일종의 "2 단계 클래스 구문 분석"입니까?
TemplateRex

4
@aschepler 예, 여기의 진단은별로 좋지 않습니다. 나는 그것을 위해 llvm.org/PR16550을 제출했습니다.
Richard Smith

@Cornstalks Clang에서 비 정적 데이터 멤버에 대한 이니셜 라이저를 구현하는 동안이 문제를 발견했습니다.
Richard Smith

0

아마도 이것이 문제 일 수 있습니다.

§12.1 5. 기본값으로 설정되고 삭제됨으로 정의되지 않은 기본 생성자는 클래스 유형 (1.8)의 객체를 생성하기 위해 odr- 사용 (3.2)되거나 첫 번째 선언 이후 명시 적으로 기본값이 설정 될 때 암시 적으로 정의됩니다.

따라서 기본 생성자는 처음 조회 할 때 생성되지만 A가 완전히 정의되지 않았으므로 A 내부의 B를 찾을 수 없기 때문에 조회가 실패합니다.


"그러므로"에 대해 잘 모르겠습니다. 분명히 B b문제가되지 않으며, 명시 적으로 선언 된 생성자를 찾는 B것은 문제가되지 않습니다. 따라서이 이유 때문에 코드를 불법으로 선언하기 전에이 경우에만 " B내부 A찾을 수 없음"을 발견하지 못하도록 여기에서 조회가 다르게 진행되어야하는 이유에 대한 정의를 확인하는 것이 좋습니다 .
moonshadow

표준에서 중첩 된 클래스를 포함하여 클래스 내 초기화 중에 클래스 정의가 완료된 것으로 간주된다는 단어를 발견했습니다. 관련성이 없어 보였기 때문에 참조를 기록하지 않았습니다.
Mark B

@moonshadow : 명령문은 odr- 사용시 암시 적으로 기본 생성자가 정의된다고 말합니다. 명시 적으로 첫 번째 선언 후에 정의됩니다. 그리고 B의 B가 생성자를 호출하지 않고, (A)의 생성자 B의 생성자 호출
FSCAN

이와 같은 것이 문제인 경우 =0에서 제거하면 코드가 여전히 유효하지 않습니다 i = 0;. 그러나 그것 없이는 =0코드가 유효하며 B()정의 내에서 사용에 대해 불평하는 단일 컴파일러를 찾을 수 없습니다 A.
aschepler
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.