เกิดข้อผิดพลาดเมื่อใช้การเตรียมใช้งานในคลาสของสมาชิกข้อมูลที่ไม่คงที่และตัวสร้างคลาสที่ซ้อนกัน


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 นอกเหนือจากข้อเท็จจริงที่ว่า g ++ 4.7.2 segfaults ในโค้ดนี้ ( http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57770 ) คอมไพเลอร์ที่ผ่านการทดสอบอื่น ๆ ยังให้ข้อความแสดงข้อผิดพลาดที่ไม่ได้อธิบายอะไรมาก

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())
                       ^

เสียงดัง ++ 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ถึงรหัสนี้ ...
Fred Foo

2
(ข้อผิดพลาด C2864: 'A :: B :: i': สมาชิกข้อมูลอินทิกรัล const คงที่เท่านั้นที่สามารถเริ่มต้นได้ภายในคลาส) คือสิ่งที่ VC2010 กล่าว ผลลัพธ์นั้นเห็นด้วยกับ g ++ เสียงดังกล่าวเช่นกันแม้ว่าจะมีเหตุผลน้อยกว่ามาก คุณไม่สามารถเริ่มต้นตัวแปร struct ได้โดยการทำเว้นแต่เป็นint i = 0 static const int i = 0
Chris Cooper

@Borgleader: BTW ฉันจะหลีกเลี่ยงสิ่งล่อใจที่จะคิดว่านิพจน์B()เป็นการเรียกใช้ฟังก์ชันไปยังตัวสร้าง คุณไม่เคย "เรียก" ผู้สร้างโดยตรง คิดว่านี่เป็นไวยากรณ์พิเศษที่สร้างชั่วคราวB... และตัวสร้างถูกเรียกเป็นเพียงส่วนหนึ่งของกระบวนการนั้นซึ่งอยู่ลึกเข้าไปในกลไกที่ตามมา
Lightness Races ใน Orbit

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()เป็นปริยายnoexceptIFF A()ไม่เคยโยนและA()ไม่เคยโยน IFF B::B()เป็นไม่ได้ noexceptมีวงจรอื่น ๆ และความขัดแย้งในพื้นที่นี้

นี้จะติดตามโดยประเด็นหลัก1360และ1397 โปรดสังเกตโดยเฉพาะหมายเหตุนี้ในปัญหาหลัก 1397:

บางทีวิธีที่ดีที่สุดในการจัดการกับสิ่งนี้คือการทำให้เป็นรูปแบบที่ไม่ถูกต้องสำหรับตัวเริ่มต้นสมาชิกข้อมูลที่ไม่คงที่เพื่อใช้ตัวสร้างเริ่มต้นของคลาส

นี่เป็นกรณีพิเศษของกฎที่ฉันใช้ใน Clang เพื่อแก้ไขปัญหานี้ กฎของ Clang คือไม่สามารถใช้ตัวสร้างเริ่มต้นที่เป็นค่าเริ่มต้นสำหรับคลาสได้ก่อนที่จะมีการแยกวิเคราะห์ข้อมูลเริ่มต้นสมาชิกข้อมูลที่ไม่คงที่สำหรับคลาสนั้น ดังนั้น Clang จึงออกการวินิจฉัยที่นี่:

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

... เนื่องจากเสียงดังแยกวิเคราะห์อาร์กิวเมนต์เริ่มต้นก่อนที่จะแยกวิเคราะห์ค่าเริ่มต้นเริ่มต้นและอาร์กิวเมนต์เริ่มต้นนี้ต้องการให้ตัวBเริ่มต้นเริ่มต้นได้รับการแยกวิเคราะห์แล้ว (เพื่อที่จะกำหนดโดยนัยB::B())


ดีแล้วที่รู้. แต่ข้อความแสดงข้อผิดพลาดยังคงทำให้เข้าใจผิดเนื่องจากตัวสร้างไม่ได้ "ใช้โดยตัวเริ่มต้นข้อมูลสมาชิกแบบไม่คงที่"
aschepler

คุณทราบเรื่องนี้เนื่องจากประสบการณ์ที่ผ่านมาโดยเฉพาะกับปัญหานี้หรือเพียงแค่อ่านมาตรฐาน (และรายการข้อบกพร่อง) อย่างละเอียด นอกจากนี้ +1
Cornstalks

+1 สำหรับคำตอบโดยละเอียดนี้ แล้วทางออกจะเป็นอย่างไร? "การแยกวิเคราะห์คลาส 2 เฟส" บางประเภทที่การแยกวิเคราะห์สมาชิกชั้นนอกที่ขึ้นอยู่กับคลาสชั้นในจะล่าช้าจนกว่าชั้นเรียนภายในจะถูกสร้างขึ้นอย่างสมบูรณ์?
TemplateRex

4
@aschepler ใช่การวินิจฉัยที่นี่ไม่ค่อยดีนัก ฉันได้ยื่น llvm.org/PR16550 ไปแล้ว
Richard Smith

@Cornstalks ฉันค้นพบปัญหานี้ในขณะที่ใช้ initializers สำหรับสมาชิกข้อมูลแบบไม่คงที่ใน Clang
Richard Smith

0

อาจเป็นปัญหา:

§12.1 5. คอนสตรัคเตอร์ดีฟอลต์ที่เป็นค่าดีฟอลต์และไม่ได้กำหนดเป็นลบถูกกำหนดโดยปริยายเมื่อถูกใช้ (3.2) เพื่อสร้างอ็อบเจ็กต์ประเภทคลาส (1.8) หรือเมื่อมีการดีฟอลต์อย่างชัดเจนหลังจากการประกาศครั้งแรก

ดังนั้นตัวสร้างเริ่มต้นจะถูกสร้างขึ้นเมื่อค้นหาครั้งแรก แต่การค้นหาจะล้มเหลวเนื่องจาก A ไม่ได้กำหนดไว้อย่างสมบูรณ์ดังนั้นจึงไม่พบ B ภายใน A


ฉันไม่แน่ใจเกี่ยวกับเรื่องนั้น "เพราะฉะนั้น" เห็นได้ชัดว่าB bไม่ใช่ปัญหาและการค้นหาวิธีการที่ชัดเจน / ตัวสร้างที่ประกาศไว้อย่างชัดเจนBไม่ใช่ปัญหา ดังนั้นจึงเป็นการดีที่จะเห็นคำจำกัดความว่าทำไมการค้นหาจึงควรดำเนินการแตกต่างกันที่นี่เพื่อให้ " ไม่พบBภายในA" ในกรณีนี้เพียงกรณีเดียว แต่ไม่ใช่กรณีอื่น ๆ ก่อนที่เราจะสามารถประกาศรหัสผิดด้วยเหตุผลนี้
moonshadow

ฉันพบคำในมาตรฐานที่ถือว่าการกำหนดคลาสเสร็จสมบูรณ์ในระหว่างการเริ่มต้นในคลาสรวมถึงภายในคลาสที่ซ้อนกัน ฉันไม่ได้กังวลที่จะบันทึกข้อมูลอ้างอิงเนื่องจากดูเหมือนจะไม่เกี่ยวข้อง
Mark B

@moonshadow: คำสั่งกล่าวว่า constructor ที่ผิดนัดโดยปริยายถูกกำหนดเมื่อใช้ odr- กำหนดไว้อย่างชัดเจนหลังจากการประกาศครั้งแรก และ B b ไม่เรียกตัวสร้างตัวสร้างของ A เรียกตัวสร้างของ B
fscan

ถ้ามีอะไรเช่นนี้เป็นปัญหาที่รหัสจะยังคงไม่ถูกต้องถ้าคุณลบจาก=0 i = 0;แต่หากไม่มี=0รหัสนั้นรหัสจะถูกต้องและคุณจะไม่พบคอมไพเลอร์เดียวที่บ่นเกี่ยวกับการใช้B()ภายในคำจำกัดความของA.
aschepler
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.