C ++ 11 ช่วยให้สามารถเริ่มต้นในคลาสของสมาชิกที่ไม่คงที่และไม่ใช่ const อะไรเปลี่ยนไป?


89

ก่อน C ++ 11 เราสามารถดำเนินการเริ่มต้นในคลาสเฉพาะกับสมาชิก const คงที่ของประเภทอินทิกรัลหรือการแจงนับ Stroustrup กล่าวถึงสิ่งนี้ในคำถามที่พบบ่อยเกี่ยวกับ C ++โดยให้ตัวอย่างต่อไปนี้:

class Y {
  const int c3 = 7;           // error: not static
  static int c4 = 7;          // error: not const
  static const float c5 = 7;  // error: not integral
};

และเหตุผลต่อไปนี้:

เหตุใดจึงมีข้อ จำกัด ที่ไม่สะดวกเหล่านี้? โดยทั่วไปคลาสจะถูกประกาศในไฟล์ส่วนหัวและโดยทั่วไปไฟล์ส่วนหัวจะรวมอยู่ในหน่วยการแปลจำนวนมาก อย่างไรก็ตามเพื่อหลีกเลี่ยงกฎตัวเชื่อมโยงที่ซับซ้อน C ++ ต้องการให้ทุกออบเจ็กต์มีนิยามเฉพาะ กฎนั้นจะเสียหาก C ++ อนุญาตให้มีการกำหนดเอนทิตีในคลาสที่จำเป็นต้องเก็บไว้ในหน่วยความจำเป็นวัตถุ

อย่างไรก็ตาม C ++ 11 ผ่อนคลายข้อ จำกัด เหล่านี้ทำให้สามารถเริ่มต้นในคลาสของสมาชิกที่ไม่คงที่ (§12.6.2 / 8):

ในคอนสตรัคเตอร์ที่ไม่ใช่การมอบหมายหากสมาชิกข้อมูลที่ไม่คงที่หรือคลาสพื้นฐานไม่ได้กำหนดโดยmem-initializer-id (รวมถึงกรณีที่ไม่มีmem-initializer-listเนื่องจากตัวสร้างไม่มีctor-initializer ) และเอนทิตีไม่ใช่คลาสพื้นฐานเสมือนของคลาสนามธรรม (10.4) ดังนั้น

  • ถ้าเอนทิตีเป็นสมาชิกข้อมูลที่ไม่คงที่ที่มีตัวเริ่มต้นแบบรั้งหรือเท่ากับค่าเริ่มต้นเอนทิตีจะถูกเตรียมใช้งานตามที่ระบุใน 8.5
  • มิฉะนั้นหากเอนทิตีเป็นสมาชิกตัวแปร (9.5) จะไม่มีการเตรียมใช้งาน
  • มิฉะนั้นเอนทิตีเป็นค่าเริ่มต้นเริ่มต้น (8.5)

ส่วน 9.4.2 ยังอนุญาตให้เริ่มต้นในคลาสของสมาชิกคงที่ที่ไม่ใช่ const หากมีการทำเครื่องหมายด้วยตัวconstexprระบุ

แล้วเกิดอะไรขึ้นกับสาเหตุของข้อ จำกัด ที่เรามีใน C ++ 03? เราเพียงแค่ยอมรับ "กฎตัวเชื่อมโยงที่ซับซ้อน" หรือมีการเปลี่ยนแปลงอย่างอื่นที่ทำให้ใช้งานได้ง่ายขึ้น?


5
ไม่มีอะไรเกิดขึ้น. คอมไพเลอร์เติบโตขึ้นอย่างชาญฉลาดด้วยเทมเพลตส่วนหัวเท่านั้นดังนั้นตอนนี้จึงเป็นส่วนขยายที่ค่อนข้างง่าย
Öö Tiib

น่าสนใจพอกับ IDE ของฉันเมื่อฉันเลือกก่อนการรวบรวม C ++ 11 ฉันได้รับอนุญาตให้เริ่มต้นสมาชิกอินทิกรัล const แบบไม่คงที่
Dean P

คำตอบ:


67

คำตอบสั้น ๆ คือพวกเขาเก็บตัวเชื่อมโยงไว้เหมือนเดิมด้วยค่าใช้จ่ายในการทำให้คอมไพเลอร์ยังคงซับซ้อนกว่าเดิม

กล่าวคือแทนที่จะทำให้เกิดคำจำกัดความหลายคำเพื่อให้ตัวเชื่อมโยงเรียงลำดับออก แต่ก็ยังคงให้ผลลัพธ์ในคำจำกัดความเดียวและคอมไพเลอร์ต้องเรียงลำดับออก

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

class X { 
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}
};

ตอนนี้กฎพิเศษ ณ จุดนี้จะจัดการกับค่าที่ใช้ในการเริ่มต้นaเมื่อคุณใช้ตัวสร้างที่ไม่ใช่ค่าเริ่มต้น คำตอบนั้นค่อนข้างง่าย: หากคุณใช้ตัวสร้างที่ไม่ได้ระบุค่าอื่นใดค่านั้น1234จะถูกใช้เพื่อเริ่มต้นa- แต่ถ้าคุณใช้ตัวสร้างที่ระบุค่าอื่น ๆ ค่านั้น1234จะถูกละเว้นโดยทั่วไป

ตัวอย่างเช่น:

#include <iostream>

class X { 
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}

    friend std::ostream &operator<<(std::ostream &os, X const &x) { 
        return os << x.a;
    }
};

int main() { 
    X x;
    X y{5678};

    std::cout << x << "\n" << y;
    return 0;
}

ผลลัพธ์:

1234
5678

1
ดูเหมือนว่าก่อนหน้านี้จะค่อนข้างเป็นไปได้ มันทำให้งานเขียนคอมไพเลอร์ยากขึ้น นั่นเป็นคำสั่งที่ยุติธรรมหรือไม่?
allyourcode

10
@allyourcode: ใช่และไม่ใช่ ใช่มันทำให้การเขียนคอมไพเลอร์ยากขึ้น แต่ไม่มีเพราะมันยังทำให้การเขียน c ++ เปคค่อนข้างยาก
Jerry Coffin

วิธีการเริ่มต้นสมาชิกชั้นเรียนแตกต่างกันหรือไม่: int x = 7; หรือ int x {7};?
mbaros

9

ฉันเดาว่าอาจมีการเขียนเหตุผลก่อนที่จะสรุปเทมเพลต หลังจาก "กฎตัวเชื่อมโยงที่ซับซ้อน" ทั้งหมดที่จำเป็นสำหรับการเริ่มต้นในคลาสของสมาชิกแบบคงที่นั้น / มีความจำเป็นสำหรับ C ++ 11 เพื่อรองรับสมาชิกแบบคงที่

พิจารณา

struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed,
                                                   // thanks @Kapil for pointing that out

// vs.

template <class T>
struct B { static int s; }

template <class T>
int B<T>::s = ::ComputeSomething();

// or

template <class T>
void Foo()
{
    static int s = ::ComputeSomething();
    s++;
    std::cout << s << "\n";
}

ปัญหาสำหรับคอมไพลเลอร์จะเหมือนกันในทั้งสามกรณี: หน่วยการแปลใดควรแสดงนิยามsและรหัสที่จำเป็นในการเริ่มต้น วิธีแก้ปัญหาง่ายๆคือปล่อยมันไปทุกที่และปล่อยให้ตัวเชื่อมโยงจัดการ นั่นเป็นเหตุผลที่ Linkers __declspec(selectany)ได้รับการสนับสนุนอยู่แล้วสิ่งที่ต้องการ มันคงเป็นไปไม่ได้ที่จะใช้ C ++ 03 ถ้าไม่มีมัน และนั่นเป็นสาเหตุที่ไม่จำเป็นต้องขยายตัวเชื่อมโยง

เพื่อให้ตรงไปตรงมามากขึ้น: ฉันคิดว่าการให้เหตุผลในมาตรฐานเดิมนั้นผิดธรรมดา


อัปเดต

ดังที่ Kapil ชี้ให้เห็นตัวอย่างแรกของฉันไม่ได้รับอนุญาตในมาตรฐานปัจจุบัน (C ++ 14) ฉันทิ้งมันไว้เพราะ IMO เป็นกรณีที่ยากที่สุดสำหรับการนำไปใช้งาน (คอมไพเลอร์ตัวเชื่อมโยง) จุดของฉันคือ: แม้กระทั่งว่ากรณีใด ๆ ที่ไม่หนักกว่าสิ่งที่ได้รับอนุญาตแล้วเช่นเมื่อใช้แม่แบบ


ความอัปยศนี้ไม่ได้รับการโหวตเพิ่มเนื่องจากฟีเจอร์ C ++ 11 หลายอย่างมีความคล้ายคลึงกันซึ่งคอมไพเลอร์ได้รวมความสามารถหรือการปรับแต่งที่จำเป็นไว้แล้ว
Alex Court

@AlexCourt ฉันเขียนคำตอบนี้เมื่อเร็ว ๆ นี้ คำถามและคำตอบของเจอร์รี่มาจากปี 2012 แม้ว่า ฉันเดาว่านั่นเป็นสาเหตุที่คำตอบของฉันไม่ได้รับความสนใจมากนัก
Paul Groke

1
สิ่งนี้จะไม่เป็นไปตาม "struct A {static int s = :: ComputeSomething ();}" เนื่องจากเฉพาะ const แบบคงที่เท่านั้นที่สามารถเริ่มต้นในคลาส
PapaDiHatti

8

ในทางทฤษฎีSo why do these inconvenient restrictions exist?...เหตุผลนั้นถูกต้อง แต่สามารถข้ามได้อย่างง่ายดายและนี่คือสิ่งที่ C ++ 11 ทำ

เมื่อคุณรวมไฟล์เพียงแค่รวมไฟล์และไม่คำนึงถึงการเริ่มต้นใด ๆ สมาชิกจะเริ่มต้นเมื่อคุณสร้างอินสแตนซ์ของชั้นเรียนเท่านั้น

กล่าวอีกนัยหนึ่งการเริ่มต้นยังคงเชื่อมโยงกับตัวสร้างเพียงสัญกรณ์แตกต่างกันและสะดวกกว่า หากไม่ได้เรียกตัวสร้างค่าจะไม่เริ่มต้น

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

สิ่งนี้เห็นได้ชัดจากคำถามที่พบบ่อยของ Stroustrup เองใน C ++ 11


Re "ถ้าไม่เรียกตัวสร้างค่าจะไม่เริ่มต้น": ฉันจะหลีกเลี่ยงการเริ่มต้นของสมาชิกY::c3ในคำถามได้อย่างไร? ตามที่ฉันเข้าใจc3จะเริ่มต้นเสมอเว้นแต่จะมีตัวสร้างที่แทนที่ค่าเริ่มต้นที่กำหนดในการประกาศ
Peter - คืนสถานะ Monica
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.