Aggregates และ PODs คืออะไรและทำไมพิเศษ?


548

นี้คำถามที่พบบ่อยเป็นเรื่องเกี่ยวกับปริมาณและ PODs และครอบคลุมวัสดุดังต่อไปนี้:

  • อะไรคือมวล ?
  • อะไรคือPOD s (ข้อมูล Plain Old)?
  • พวกเขาเกี่ยวข้องกันอย่างไร
  • พวกเขาพิเศษอย่างไรและทำไม
  • การเปลี่ยนแปลงสำหรับ C ++ 11 คืออะไร


อาจกล่าวได้ว่าแรงจูงใจที่อยู่เบื้องหลังคำจำกัดความเหล่านี้เป็นเรื่องคร่าว ๆ : POD == memcpy'able, Aggregate == รวมกัน - เริ่มต้นได้?
Ofek Shilon

คำตอบ:


571

อ่านอย่างไร:

บทความนี้ค่อนข้างยาว หากคุณต้องการทราบเกี่ยวกับทั้งมวลรวมและ PODs (ข้อมูลเก่าธรรมดา) ต้องใช้เวลาและอ่านมัน หากคุณสนใจเพียงแค่มวลรวมอ่านส่วนแรกเท่านั้น หากคุณสนใจเฉพาะ POD คุณต้องอ่านคำนิยามความหมายและตัวอย่างของมวลรวมก่อนจากนั้นคุณอาจข้ามไปยัง POD แต่ฉันขอแนะนำให้อ่านส่วนแรกอย่างครบถ้วน แนวคิดของมวลรวมเป็นสิ่งจำเป็นสำหรับการกำหนด POD หากคุณพบข้อผิดพลาดใด ๆ (แม้เล็กน้อยรวมถึงไวยากรณ์, โวหาร, การจัดรูปแบบ, ไวยากรณ์, ฯลฯ ) โปรดออกความคิดเห็นฉันจะแก้ไข

คำตอบนี้ใช้กับ C ++ 03 สำหรับมาตรฐาน C ++ อื่น ๆ ดูที่:

มวลรวมคืออะไรและเพราะเหตุใดจึงพิเศษ

การนิยามอย่างเป็นทางการจากมาตรฐานC ++ ( C ++ 03 8.5.1 §1 ) :

การรวมเป็นอาร์เรย์หรือคลาส (ข้อ 9) ที่ไม่มีตัวสร้างที่ประกาศโดยผู้ใช้ (12.1) ไม่มีสมาชิกข้อมูลส่วนตัวหรือป้องกันไม่คงที่ (มาตรา 11) ไม่มีคลาสพื้นฐาน (ข้อ 10) และไม่มีฟังก์ชั่นเสมือน (10.3) )

งั้นเรามาแยกคำนิยามนี้กัน ก่อนอื่นอาร์เรย์ใด ๆ จะเป็นผลรวม ชั้นเรียนอาจรวมกันได้หาก…รอ! ไม่มีอะไรพูดเกี่ยวกับ structs หรือ unions พวกมันรวมตัวกันไม่ได้เหรอ? ใช่พวกเขาสามารถ ใน C ++ คำนี้classหมายถึงคลาสโครงสร้างและสหภาพทั้งหมด ดังนั้นคลาส (หรือ struct หรือ union) จึงเป็นการรวมกันถ้าหากมันเป็นไปตามเกณฑ์จากคำจำกัดความข้างต้น เกณฑ์เหล่านี้หมายความว่าอย่างไร

  • นี่ไม่ได้หมายความว่าคลาสรวมจะไม่มีคอนสตรัคเตอร์ในความเป็นจริงมันสามารถมีคอนสตรัคเตอร์เริ่มต้นและ / หรือตัวสร้างการคัดลอกตราบใดที่พวกเขาถูกประกาศโดยปริยายโดยคอมไพเลอร์และไม่ชัดเจนโดยผู้ใช้

  • ไม่มีส่วนตัวหรือการคุ้มครองข้อมูลสมาชิกไม่คงที่ คุณสามารถมีฟังก์ชั่นส่วนตัวและสมาชิกที่ได้รับการป้องกันได้มาก (แต่ไม่ใช่คอนสตรัคเตอร์) รวมถึงสมาชิกข้อมูลส่วนตัวและฟังก์ชั่นสแตติกที่มีการป้องกันหรือส่วนตัวตามที่คุณต้องการและไม่ละเมิดกฎสำหรับคลาสรวม

  • คลาสรวมสามารถมีโอเปอเรเตอร์ที่ได้รับมอบหมายจากผู้ใช้ / ผู้ใช้กำหนดเองและ / หรือ destructor

  • อาร์เรย์เป็นผลรวมแม้ว่าจะเป็นอาร์เรย์ของประเภทคลาสที่ไม่รวม

ตอนนี้ลองมาดูตัวอย่าง:

class NotAggregate1
{
  virtual void f() {} //remember? no virtual functions
};

class NotAggregate2
{
  int x; //x is private by default and non-static 
};

class NotAggregate3
{
public:
  NotAggregate3(int) {} //oops, user-defined constructor
};

class Aggregate1
{
public:
  NotAggregate1 member1;   //ok, public member
  Aggregate1& operator=(Aggregate1 const & rhs) {/* */} //ok, copy-assignment  
private:
  void f() {} // ok, just a private function
};

คุณได้รับความคิด ตอนนี้เรามาดูกันว่าผลรวมมีความพิเศษอย่างไร {}พวกเขาแตกต่างจากการเรียนที่ไม่รวมสามารถเริ่มต้นได้ด้วยวงเล็บปีกกา ไวยากรณ์เริ่มต้นนี้เป็นที่รู้จักกันทั่วไปสำหรับอาร์เรย์และเราเพิ่งเรียนรู้ว่าสิ่งเหล่านี้เป็นผลรวม ดังนั้นเรามาเริ่มด้วยกัน

Type array_name[n] = {a1, a2, …, am};

ถ้า (m == n)
ฉัน THองค์ประกอบของอาร์เรย์จะเริ่มต้นกับฉัน
อื่นถ้า (m <n)
องค์ประกอบมแรกของอาร์เรย์จะเริ่มต้นด้วย 1เป็น 2 ... เป็นเมตรและอื่น ๆn - mองค์ประกอบ ถ้าเป็นไปได้ค่าเริ่มต้น (ดูด้านล่างสำหรับคำอธิบายของคำ
อื่น )ถ้า (m> n)
คอมไพเลอร์จะออกข้อผิดพลาด
อื่น (นี่คือกรณีที่ n ไม่ได้ระบุint a[] = {1, 2, 3};
ขนาดเหมือนกัน)อาร์เรย์ (n) จะถือว่าเท่ากับ m ดังนั้นจึงint a[] = {1, 2, 3};เท่ากับint a[3] = {1, 2, 3};

เมื่อวัตถุประเภทสเกลาร์ (เป็นbool, int, char, doubleตัวชี้ ฯลฯ ) เป็นมูลค่าเริ่มต้นมันหมายความว่ามันจะเริ่มต้นได้ด้วย0สำหรับประเภทที่ ( falseสำหรับbool, 0.0สำหรับdoubleฯลฯ ) เมื่อออบเจ็กต์ประเภทคลาสที่มีคอนสตรัคเตอร์เริ่มต้นที่ผู้ใช้ประกาศถูกกำหนดค่าเริ่มต้นมันจะเรียกคอนสตรัคเตอร์เริ่มต้น ถ้าคอนสตรัคเตอร์เริ่มต้นถูกกำหนดโดยปริยายแล้วสมาชิก nonstatic ทั้งหมดจะถูกกำหนดค่าเริ่มต้นซ้ำ คำจำกัดความนี้ไม่ถูกต้องและค่อนข้างไม่ถูกต้อง แต่ควรให้แนวคิดพื้นฐานแก่คุณ การอ้างอิงไม่สามารถกำหนดค่าเริ่มต้นได้ การกำหนดค่าเริ่มต้นสำหรับคลาสที่ไม่มีการรวมสามารถล้มเหลวได้ตัวอย่างเช่นหากคลาสนั้นไม่มีคอนสตรัคเตอร์เริ่มต้นที่เหมาะสม

ตัวอย่างของการเริ่มต้นอาร์เรย์:

class A
{
public:
  A(int) {} //no default constructor
};
class B
{
public:
  B() {} //default constructor available
};
int main()
{
  A a1[3] = {A(2), A(1), A(14)}; //OK n == m
  A a2[3] = {A(2)}; //ERROR A has no default constructor. Unable to value-initialize a2[1] and a2[2]
  B b1[3] = {B()}; //OK b1[1] and b1[2] are value initialized, in this case with the default-ctor
  int Array1[1000] = {0}; //All elements are initialized with 0;
  int Array2[1000] = {1}; //Attention: only the first element is 1, the rest are 0;
  bool Array3[1000] = {}; //the braces can be empty too. All elements initialized with false
  int Array4[1000]; //no initializer. This is different from an empty {} initializer in that
  //the elements in this case are not value-initialized, but have indeterminate values 
  //(unless, of course, Array4 is a global array)
  int array[2] = {1, 2, 3, 4}; //ERROR, too many initializers
}

ตอนนี้เรามาดูวิธีการรวมคลาสสามารถเริ่มต้นด้วยวงเล็บปีกกา ค่อนข้างเหมือนกัน แทนที่จะเป็นองค์ประกอบอาร์เรย์เราจะเริ่มต้นสมาชิกข้อมูลที่ไม่คงที่ตามลำดับของการปรากฏตัวของพวกเขาในการกำหนดระดับ (พวกเขาทั้งหมดสาธารณะตามคำนิยาม) หากมี initializers น้อยกว่าสมาชิกส่วนที่เหลือจะถูกกำหนดค่าเริ่มต้น หากไม่สามารถกำหนดค่าเริ่มต้นให้กับหนึ่งในสมาชิกที่ไม่ได้เริ่มต้นอย่างชัดเจนเราจะได้รับข้อผิดพลาดในการรวบรวมเวลา หากมี initializers มากกว่าที่จำเป็นเราจะได้รับข้อผิดพลาดในการคอมไพล์เช่นกัน

struct X
{
  int i1;
  int i2;
};
struct Y
{
  char c;
  X x;
  int i[2];
  float f; 
protected:
  static double d;
private:
  void g(){}      
}; 

Y y = {'a', {10, 20}, {20, 30}};

ในตัวอย่างข้างต้นy.cจะเริ่มต้นด้วย'a', y.x.i1กับ10, y.x.i2กับ20, y.i[0]กับ20, y.i[1]ด้วย30และเป็นมูลค่าเริ่มต้นที่เป็นเริ่มต้นได้ด้วยy.f 0.0สมาชิกคงได้รับการคุ้มครองไม่ได้เริ่มต้นที่ทุกคนเพราะมันเป็นdstatic

สหภาพการรวมจะแตกต่างกันซึ่งคุณสามารถเริ่มต้นได้เฉพาะสมาชิกแรกของพวกเขาด้วยเครื่องหมายปีกกา ฉันคิดว่าถ้าคุณมีความก้าวหน้าเพียงพอใน C ++ เพื่อพิจารณาการใช้สหภาพ (การใช้งานของพวกเขาอาจเป็นอันตรายมากและต้องพิจารณาอย่างรอบคอบ) คุณสามารถค้นหากฎสำหรับสหภาพในมาตรฐานของคุณเอง :)

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

เพียงพอเกี่ยวกับมวลรวม ตอนนี้เราสามารถกำหนดชุดประเภทที่เข้มงวดเพื่อปัญญา POD

POD คืออะไรและทำไมจึงเป็นพิเศษ

การนิยามอย่างเป็นทางการจากมาตรฐานC ++ ( C ++ 03 9 §4 ) :

POD-struct เป็นคลาสรวมที่ไม่มีสมาชิกข้อมูลที่ไม่คงที่ประเภท non-POD-struct, non-POD-union (หรืออาเรย์ประเภทดังกล่าว) หรือการอ้างอิงและไม่มีผู้ประกอบการมอบหมายการคัดลอกที่กำหนดโดยผู้ใช้และไม่มี destructor ที่ผู้ใช้กำหนด ในทำนองเดียวกัน POD-union เป็นสหภาพรวมที่ไม่มีสมาชิกข้อมูลที่ไม่คงที่ประเภท non-POD-struct, non-POD-union (หรืออาร์เรย์ประเภทดังกล่าว) หรือการอ้างอิงและไม่มีผู้ประกอบการมอบหมายการคัดลอกที่ผู้ใช้กำหนด และไม่มี destructor ที่ผู้ใช้กำหนด คลาส POD เป็นคลาสที่เป็น POD-struct หรือ POD-union

ว้าวคนนี้แกร่งกว่าที่จะแยกวิเคราะห์ใช่ไหม? :) ปล่อยให้สหภาพแรงงานออกมา (ในบริเวณเดียวกับด้านบน) และใช้ถ้อยคำใหม่ในวิธีที่ชัดเจนขึ้น:

คลาสรวมเรียกว่า POD หากไม่มีโอเปอเรเตอร์การคัดลอกที่กำหนดโดยผู้ใช้และ destructor และไม่มีสมาชิกที่ไม่ใช่สมาชิกของมันคือคลาส non-POD, อาเรย์ของ non-POD หรือการอ้างอิง

คำจำกัดความนี้หมายถึงอะไร (ฉันพูดถึงPODย่อมาจากPlain Old Dataหรือไม่)

  • คลาส POD ทั้งหมดเป็นผลรวมหรือถ้าจะให้เป็นอย่างอื่นถ้าคลาสนั้นไม่ใช่ผลรวมก็ไม่แน่ใจว่าเป็น POD
  • คลาสเช่นเดียวกับ structs สามารถเป็น POD แม้ว่าคำมาตรฐานคือ POD-struct สำหรับทั้งสองกรณี
  • เช่นเดียวกับในกรณีของมวลรวมมันไม่สำคัญว่าสมาชิกแบบคงที่ในชั้นเรียนจะมีอะไร

ตัวอย่าง:

struct POD
{
  int x;
  char y;
  void f() {} //no harm if there's a function
  static std::vector<char> v; //static members do not matter
};

struct AggregateButNotPOD1
{
  int x;
  ~AggregateButNotPOD1() {} //user-defined destructor
};

struct AggregateButNotPOD2
{
  AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};

คลาส POD, POD-unions, ประเภทสเกลาร์, และอาร์เรย์ของประเภทดังกล่าวเรียกว่าประเภท POD
ฝักเป็นพิเศษในหลาย ๆ ฉันจะให้ตัวอย่าง

  • POD-classes นั้นใกล้เคียงกับ C struct มากที่สุด ซึ่งแตกต่างจากพวกเขา POD สามารถมีฟังก์ชั่นสมาชิกและสมาชิกคงที่โดยพลการ แต่ทั้งสองไม่เปลี่ยนแปลงรูปแบบหน่วยความจำของวัตถุ ดังนั้นหากคุณต้องการเขียนไลบรารี่แบบพกพาที่มากขึ้นหรือน้อยลงที่สามารถใช้งานได้จาก C และแม้แต่. NET คุณควรพยายามทำให้ฟังก์ชั่นที่ส่งออกทั้งหมดใช้และส่งคืนพารามิเตอร์ประเภท POD เท่านั้น

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

  • สำหรับออบเจกต์ของประเภท POD จะมีการรับประกันตามมาตรฐานที่เมื่อคุณmemcpyเนื้อหาของวัตถุของคุณเป็นอาร์เรย์ของถ่านหรือถ่านที่ไม่ได้ลงนามแล้วmemcpyเนื้อหากลับเข้าไปในวัตถุของคุณวัตถุจะเก็บค่าเดิม โปรดทราบว่าไม่มีการรับประกันดังกล่าวสำหรับวัตถุที่ไม่ใช่ POD นอกจากนี้คุณยังสามารถคัดลอกวัตถุ POD memcpyกับ ตัวอย่างต่อไปนี้สมมติว่า T เป็นชนิด POD:

    #define N sizeof(T)
    char buf[N];
    T obj; // obj initialized to its original value
    memcpy(buf, &obj, N); // between these two calls to memcpy,
    // obj might be modified
    memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
    // holds its original value
  • คำสั่ง goto อย่างที่คุณอาจจะรู้ว่ามันผิดกฎหมาย (คอมไพเลอร์ควรออกข้อผิดพลาด) เพื่อทำการกระโดดผ่าน goto จากจุดที่ตัวแปรบางตัวยังไม่อยู่ในขอบเขตจนถึงจุดที่มันอยู่ในขอบเขตแล้ว ข้อ จำกัด นี้ใช้เฉพาะในกรณีที่ตัวแปรเป็นประเภทที่ไม่ใช่ POD ในตัวอย่างต่อไปนี้f()เป็นรูปแบบไม่ดีในขณะที่g()มีรูปแบบที่ดี โปรดทราบว่าคอมไพเลอร์ของ Microsoft เปิดเสรีเกินไปกับกฎนี้ - เพียงแค่ออกคำเตือนในทั้งสองกรณี

    int f()
    {
      struct NonPOD {NonPOD() {}};
      goto label;
      NonPOD x;
    label:
      return 0;
    }
    
    int g()
    {
      struct POD {int i; char c;};
      goto label;
      POD x;
    label:
      return 0;
    }
  • มีการรับประกันว่าจะไม่มีช่องว่างภายในในการเริ่มต้นของวัตถุ POD ในคำอื่น ๆ ถ้า POD ระดับของสมาชิกคนแรกเป็นประเภท T, คุณสามารถได้อย่างปลอดภัยreinterpret_castจากA*ไปT*และได้รับการชี้ไปยังสมาชิกคนแรกและในทางกลับกัน

รายการไปบนและบน…

ข้อสรุป

เป็นสิ่งสำคัญที่จะต้องเข้าใจว่า POD คืออะไรเพราะมีคุณสมบัติทางภาษามากมายดังที่คุณเห็น


3
คำตอบที่ดี ความคิดเห็นที่: "ถ้าคอนสตรัคเตอร์เริ่มต้นถูกกำหนดโดยปริยายแล้วสมาชิก nonstatic ทั้งหมดจะถูกกำหนดค่าเริ่มต้นซ้ำ" และ "การกำหนดค่าเริ่มต้นสำหรับคลาสที่ไม่รวมกันอาจล้มเหลวได้ตัวอย่างเช่นหากคลาสนั้นไม่มีคอนสตรัคเตอร์เริ่มต้นที่เหมาะสม" ไม่ถูกต้อง: การกำหนดค่าเริ่มต้นของคลาสด้วยตัวสร้างค่าเริ่มต้นที่ประกาศโดยปริยายไม่จำเป็นต้องใช้ตัวสร้างค่าเริ่มต้นที่กำหนดโดยปริยาย ดังนั้นที่ได้รับ (แทรกprivate:ตามความเหมาะสม): struct A { int const a; };จากนั้นA()มีรูปแบบที่ดีแม้ว่าAคำจำกัดความของคอนสตรัคเตอร์เริ่มต้นจะเป็นรูปแบบที่ไม่ดี
Johannes Schaub - litb

4
@Kev: หากคุณจัดการเพื่อบรรจุข้อมูลเดียวกันลงในคำตอบที่สั้นกว่าเราทุกคนมีความสุขมากขึ้นโหวต!
sbi

3
@Armen ยังทราบว่าคุณสามารถตอบได้หลายคำถาม แต่ละคำตอบอาจมีส่วนหนึ่งของคำตอบของคำถาม สกรูสิ่งที่ได้รับการยอมรับเครื่องหมายในความคิดของฉัน :)
34490 Johannes Schaub - litb

3
คำตอบนั้นยอดเยี่ยม ฉันยังคงกลับมายังโพสต์นี้อีกสักครั้ง โดยวิธีการเกี่ยวกับคำเตือนสำหรับ Visual Studio "คำสั่ง goto" สำหรับพ็อดมาพร้อมกับไม่รู้ถึงคอมไพเลอร์ MSVC ตามที่คุณพูดถึง แต่สำหรับคำสั่ง switch / case มันสร้างข้อผิดพลาดในการคอมไพล์ ตามแนวคิดแล้วฉันได้ทำการทดสอบ pod-checker แล้ว: stackoverflow.com/questions/12232766/test-for-pod-ness-in-c-c11/…
bruziuz

2
ในสัญลักษณ์แสดงหัวข้อย่อยที่ขึ้นต้นด้วย "อายุการใช้งานของวัตถุประเภทคลาสที่ไม่ใช่ POD เริ่มต้นเมื่อตัวสร้างเสร็จสิ้นและสิ้นสุดลงเมื่อตัวทำลายเสร็จสิ้น" ส่วนสุดท้ายควรพูดว่า "เมื่อตัวทำลายเริ่มต้น"
Quokka

457

การเปลี่ยนแปลงสำหรับ C ++ 11 คืออะไร

มวลรวม

คำจำกัดความมาตรฐานของผลรวมมีการเปลี่ยนแปลงเล็กน้อย แต่ก็ยังคงเหมือนเดิม:

การรวมเป็นอาเรย์หรือคลาส (ข้อ 9) โดยไม่มีคอนสตรัคเตอร์ที่ผู้ใช้จัดเตรียมไว้ (12.1), ไม่มีวงเล็บปีกกาหรือเท่ากับค่าเริ่มต้นสำหรับสมาชิกข้อมูลที่ไม่คงที่ (9.2), ไม่มีสมาชิกข้อมูลส่วนตัว ข้อ 11) ไม่มีคลาสพื้นฐาน (ข้อ 10) และไม่มีฟังก์ชั่นเสมือน (10.3)

ตกลงมีอะไรเปลี่ยนแปลงบ้าง

  1. ก่อนหน้านี้รวมกันจะไม่มีใช้ประกาศก่อสร้าง แต่ตอนนี้มันไม่สามารถมีผู้ใช้ให้ก่อสร้าง มีความแตกต่างหรือไม่? ใช่มีเพราะตอนนี้คุณสามารถประกาศตัวสร้างและกำหนดค่าเริ่มต้นได้:

    struct Aggregate {
        Aggregate() = default; // asks the compiler to generate the default implementation
    };

    สิ่งนี้ยังคงเป็นการรวมกันเนื่องจากตัวสร้าง (หรือฟังก์ชันสมาชิกพิเศษใด ๆ ) ที่มีค่าเริ่มต้นในการประกาศครั้งแรกนั้นไม่ได้จัดทำโดยผู้ใช้

  2. ตอนนี้การรวมไม่สามารถมีวงเล็บปีกกาหรือเท่ากับ initializersสำหรับสมาชิกข้อมูลที่ไม่คงที่ สิ่งนี้หมายความว่า? นี่เป็นเพียงเพราะมาตรฐานใหม่นี้เราสามารถเริ่มสมาชิกโดยตรงในชั้นเรียนเช่นนี้:

    struct NotAggregate {
        int x = 5; // valid in C++11
        std::vector<int> s{1,2,3}; // also valid
    };

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

ดังนั้นสิ่งที่รวมกันไม่ได้เปลี่ยนแปลงอะไรมากมายเลย มันยังคงเป็นแนวคิดพื้นฐานเดียวกันปรับให้เข้ากับคุณสมบัติใหม่

แล้ว POD ล่ะ?

POD ผ่านการเปลี่ยนแปลงมากมาย กฎก่อนหน้าจำนวนมากเกี่ยวกับ POD ได้ผ่อนคลายในมาตรฐานใหม่นี้และวิธีการให้คำจำกัดความในมาตรฐานนั้นเปลี่ยนไปอย่างสิ้นเชิง

แนวคิดของ POD คือการจับคุณสมบัติที่แตกต่างกันสองประการ:

  1. รองรับการเริ่มต้นคงที่และ
  2. การคอมไพล์ POD ใน C ++ ให้เค้าโครงเลย์เอาต์ของหน่วยความจำเดียวกับคอมไพล์ที่คอมไพล์ใน C

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

คำจำกัดความใหม่บอกโดยทั่วไปว่า POD เป็นคลาสที่ทั้งเล็กน้อยและมีเลย์เอาต์มาตรฐานและคุณสมบัตินี้จะต้องมีการเรียกซ้ำสำหรับสมาชิกข้อมูลที่ไม่คงที่ทั้งหมด:

struct POD เป็นคลาสที่ไม่รวมกันซึ่งเป็นทั้งคลาสที่ไม่สำคัญและคลาสเลย์เอาต์มาตรฐานและไม่มีสมาชิกข้อมูลที่ไม่ใช่แบบคงที่ของประเภทที่ไม่ใช่ POD struct, non-POD union (หรืออาร์เรย์ประเภทดังกล่าว) ในทำนองเดียวกัน POD union เป็นสหภาพที่เป็นทั้งคลาสที่ไม่สำคัญและคลาสเลย์เอาต์มาตรฐานและไม่มีสมาชิกข้อมูลที่ไม่ใช่แบบคงที่ที่ไม่ใช่ POD struct, non-POD union (หรืออาเรย์ประเภทดังกล่าว) คลาส POD เป็นคลาสที่เป็น POD struct หรือ POD union

เรามาดูรายละเอียดของคุณสมบัติทั้งสองแยกกัน

ชั้นเรียนเล็กน้อย

Trivialเป็นคุณสมบัติแรกที่กล่าวถึงข้างต้นคลาส trivial รองรับการเริ่มต้นแบบคงที่ หากชั้นเรียนสามารถคัดลอกได้เล็กน้อย (ชั้นเรียนที่ไม่สำคัญ) มันก็โอเคที่จะคัดลอกการเป็นตัวแทนของสถานที่ที่มีสิ่งต่าง ๆ เช่นmemcpyและคาดว่าผลลัพธ์จะเหมือนกัน

มาตรฐานกำหนดระดับชั้นเล็กน้อยดังนี้

คลาสที่คัดลอกได้เล็กน้อยคือคลาสที่:

- ไม่มีตัวสร้างสำเนาที่ไม่สำคัญ (12.8)

- ไม่มีสิ่งก่อสร้างเคลื่อนที่ที่ไม่สำคัญ (12.8)

- ไม่มีผู้ประกอบการที่ได้รับมอบหมายไม่คัดลอกเล็กน้อย (13.5.3, 12.8)

- ไม่มีผู้ประกอบการที่ได้รับมอบหมายการย้ายที่ไม่สำคัญ (13.5.3, 12.8) และ

- มีตัวทำลายเล็กน้อย (12.4)

คลาส trivial เป็นคลาสที่มีตัวสร้างปริยายเล็กน้อย (12.1) และสามารถคัดลอกได้เล็กน้อย

[ หมายเหตุ:โดยเฉพาะอย่างยิ่งคลาสที่คัดลอกได้หรือไม่สำคัญไม่มีฟังก์ชันเสมือนหรือคลาสฐานเสมือน - บันทึกย่อ ]

ดังนั้นอะไรคือสิ่งที่น่ารำคาญและไม่น่ารำคาญ?

ตัวสร้างสำเนา / ย้ายสำหรับคลาส X นั้นไม่สำคัญหากไม่มีการจัดเตรียมโดยผู้ใช้และหาก

- คลาส X ไม่มีฟังก์ชั่นเสมือน (10.3) และไม่มีคลาสฐานเสมือน (10.1) และ

- นวกรรมิกที่เลือกเพื่อคัดลอก / ย้ายแต่ละ subobject คลาสฐานโดยตรงนั้นเป็นเรื่องเล็กน้อยและ

- สำหรับสมาชิกข้อมูลที่ไม่คงที่ของ X ซึ่งเป็นประเภทคลาส (หรืออาร์เรย์ดังกล่าว) คอนสตรัคเตอร์ที่เลือกเพื่อคัดลอก / ย้ายสมาชิกนั้นเป็นเรื่องเล็กน้อย

มิฉะนั้นตัวสร้างสำเนา / ย้ายจะไม่สำคัญ

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

ความหมายของโอเปอเรเตอร์การคัดลอก / ย้ายเล็กน้อยนั้นคล้ายกันมากเพียงแค่แทนที่คำว่า "ตัวสร้าง" ด้วย "โอเปอเรเตอร์การกำหนด"

destructor เล็กน้อยยังมีคำจำกัดความที่คล้ายกันโดยมีข้อ จำกัด เพิ่มเติมที่ไม่สามารถเป็นเสมือนได้

และยังมีกฎอื่นที่คล้ายคลึงกันสำหรับตัวสร้างค่าเริ่มต้นเล็กน้อยด้วยการเพิ่มว่าตัวสร้างค่าเริ่มต้นนั้นไม่สำคัญถ้าคลาสนั้นมีสมาชิกข้อมูลที่ไม่คงที่ที่มีวงเล็บปีกกาหรือเท่ากับชื่อย่อซึ่งเราได้เห็นด้านบน

นี่คือตัวอย่างเพื่อล้างข้อมูลทุกอย่าง:

// empty classes are trivial
struct Trivial1 {};

// all special members are implicit
struct Trivial2 {
    int x;
};

struct Trivial3 : Trivial2 { // base class is trivial
    Trivial3() = default; // not a user-provided ctor
    int y;
};

struct Trivial4 {
public:
    int a;
private: // no restrictions on access modifiers
    int b;
};

struct Trivial5 {
    Trivial1 a;
    Trivial2 b;
    Trivial3 c;
    Trivial4 d;
};

struct Trivial6 {
    Trivial2 a[23];
};

struct Trivial7 {
    Trivial6 c;
    void f(); // it's okay to have non-virtual functions
};

struct Trivial8 {
     int x;
     static NonTrivial1 y; // no restrictions on static members
};

struct Trivial9 {
     Trivial9() = default; // not user-provided
      // a regular constructor is okay because we still have default ctor
     Trivial9(int x) : x(x) {};
     int x;
};

struct NonTrivial1 : Trivial3 {
    virtual void f(); // virtual members make non-trivial ctors
};

struct NonTrivial2 {
    NonTrivial2() : z(42) {} // user-provided ctor
    int z;
};

struct NonTrivial3 {
    NonTrivial3(); // user-provided ctor
    int w;
};
NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration
                                      // still counts as user-provided
struct NonTrivial5 {
    virtual ~NonTrivial5(); // virtual destructors are not trivial
};

รูปแบบมาตรฐาน

โครงร่างมาตรฐานเป็นคุณสมบัติที่สอง มาตรฐานกล่าวว่าสิ่งเหล่านี้มีประโยชน์สำหรับการสื่อสารกับภาษาอื่นและนั่นเป็นเพราะคลาสเลย์เอาต์มาตรฐานมีเลย์เอาต์หน่วยความจำแบบเดียวกันของ C struct หรือ union ที่เทียบเท่ากัน

นี่คือคุณสมบัติอื่นที่ต้องเก็บซ้ำสำหรับสมาชิกและคลาสพื้นฐานทั้งหมด และตามปกติไม่อนุญาตให้ใช้ฟังก์ชันเสมือนหรือคลาสฐานเสมือน นั่นจะทำให้รูปแบบที่เข้ากันไม่ได้กับ C

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

เมื่อใช้การสืบทอดมีเพียงคลาสเดียวในแผนผังการสืบทอดทั้งหมดที่สามารถมีสมาชิกข้อมูลแบบไม่คงที่และสมาชิกข้อมูลแบบไม่คงที่ครั้งแรกไม่สามารถเป็นประเภทคลาสพื้นฐาน (ซึ่งอาจทำลายกฎนามแฝง) คลาสเลย์เอาต์

นี่คือความหมายที่กำหนดในข้อความมาตรฐาน:

คลาสเลย์เอาต์มาตรฐานคือคลาสที่:

- ไม่มีสมาชิกข้อมูลที่ไม่คงที่ประเภทคลาสที่ไม่ได้มาตรฐาน (หรืออาร์เรย์ของประเภทดังกล่าว) หรือการอ้างอิง

- ไม่มีฟังก์ชั่นเสมือน (10.3) และไม่มีคลาสฐานเสมือน (10.1)

- มีการควบคุมการเข้าถึงเดียวกัน (ข้อ 11) สำหรับสมาชิกข้อมูลที่ไม่คงที่ทั้งหมด

- ไม่มีคลาสพื้นฐานที่ไม่มีเลย์เอาต์

- อาจไม่มีสมาชิกข้อมูลที่ไม่คงที่ในคลาสที่ได้รับมากที่สุดและที่คลาสพื้นฐานส่วนใหญ่ที่มีสมาชิกข้อมูลที่ไม่คงที่หรือไม่มีคลาสฐานที่มีสมาชิกข้อมูลที่ไม่คงที่และ

- ไม่มีคลาสพื้นฐานประเภทเดียวกันกับสมาชิกข้อมูลไม่คงที่ครั้งแรก

โครงสร้างเลย์เอาต์มาตรฐานคือคลาสเลย์เอาต์มาตรฐานที่กำหนดด้วยโครงสร้างคลาสคีย์หรือคลาสคลาสคีย์

การรวมแบบเลย์เอาต์มาตรฐานเป็นคลาสเลย์เอาต์มาตรฐานที่กำหนดด้วยการรวมคลาสคีย์

[ หมายเหตุ:คลาสเลย์เอาต์มาตรฐานมีประโยชน์สำหรับการสื่อสารกับโค้ดที่เขียนในภาษาการเขียนโปรแกรมอื่น เค้าโครงของพวกเขาถูกระบุใน 9.2 - บันทึกย่อ ]

ลองมาตัวอย่างเล็ก ๆ น้อย ๆ

// empty classes have standard-layout
struct StandardLayout1 {};

struct StandardLayout2 {
    int x;
};

struct StandardLayout3 {
private: // both are private, so it's ok
    int x;
    int y;
};

struct StandardLayout4 : StandardLayout1 {
    int x;
    int y;

    void f(); // perfectly fine to have non-virtual functions
};

struct StandardLayout5 : StandardLayout1 {
    int x;
    StandardLayout1 y; // can have members of base type if they're not the first
};

struct StandardLayout6 : StandardLayout1, StandardLayout5 {
    // can use multiple inheritance as long only
    // one class in the hierarchy has non-static data members
};

struct StandardLayout7 {
    int x;
    int y;
    StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok
};

struct StandardLayout8 {
public:
    StandardLayout8(int x) : x(x) {} // user-provided ctors are ok
// ok to have non-static data members and other members with different access
private:
    int x;
};

struct StandardLayout9 {
    int x;
    static NonStandardLayout1 y; // no restrictions on static members
};

struct NonStandardLayout1 {
    virtual f(); // cannot have virtual functions
};

struct NonStandardLayout2 {
    NonStandardLayout1 X; // has non-standard-layout member
};

struct NonStandardLayout3 : StandardLayout1 {
    StandardLayout1 x; // first member cannot be of the same type as base
};

struct NonStandardLayout4 : StandardLayout3 {
    int z; // more than one class has non-static data members
};

struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class

ข้อสรุป

ด้วยกฎใหม่เหล่านี้ประเภทอื่น ๆ สามารถเป็น POD ได้ในขณะนี้ และแม้ว่าประเภทจะไม่ใช่ POD เราสามารถใช้ประโยชน์จากคุณสมบัติ POD บางอย่างแยกกัน (ถ้าเป็นเพียงเรื่องเล็กน้อยหรือเค้าโครงมาตรฐาน)

ไลบรารีมาตรฐานมีคุณสมบัติเพื่อทดสอบคุณสมบัติเหล่านี้ในส่วนหัว<type_traits>:

template <typename T>
struct std::is_pod;
template <typename T>
struct std::is_trivial;
template <typename T>
struct std::is_trivially_copyable;
template <typename T>
struct std::is_standard_layout;

2
คุณช่วยอธิบายเพิ่มเติมกฎต่อไปนี้ได้หรือไม่: a) คลาสเลย์เอาต์มาตรฐานต้องมีสมาชิกข้อมูลที่ไม่คงที่ทั้งหมดที่มีการควบคุมการเข้าถึงเดียวกัน b) มีเพียงคลาสเดียวในแผนผังการสืบทอดทั้งหมดที่สามารถมีสมาชิกข้อมูลแบบไม่คงที่และสมาชิกข้อมูลแบบไม่คงที่รายแรกไม่สามารถเป็นประเภทคลาสพื้นฐานได้ โดยเฉพาะอย่างยิ่งมีเหตุผลอะไรสำหรับพวกเขา สำหรับกฎในภายหลังคุณสามารถให้ตัวอย่างของการแบ่งนามแฝงได้หรือไม่
Andriy Tylychko

@AndyT: ดูคำตอบของฉัน ฉันพยายามที่จะตอบความรู้ที่ดีที่สุดของฉัน
Nicol Bolas

5
อาจต้องการอัปเดตสิ่งนี้สำหรับ C ++ 14 ซึ่งลบข้อกำหนด "no brace-or-initial-initializers" สำหรับการรวม
TC

@TC ขอบคุณสำหรับ heads-up ฉันจะค้นหาการเปลี่ยนแปลงเหล่านั้นในไม่ช้าและอัปเดต
R. Martinho Fernandes

1
เกี่ยวกับนามแฝง: มีกฎโครงร่าง C ++ ที่ถ้าคลาส C มีฐานว่าง (ว่าง) และสมาชิกข้อมูลแรกของ C คือประเภท X ดังนั้นสมาชิกรายแรกจะไม่สามารถชดเชยออฟเซ็ตเดียวกันกับฐาน X ได้ มันจะได้รับ padding bytes ก่อนหน้าหากจำเป็นต้องหลีกเลี่ยง การมีสองอินสแตนซ์ของ X (หรือคลาสย่อย) ที่ที่อยู่เดียวกันอาจทำให้สิ่งต่าง ๆ ที่ต้องแยกแยะอินสแตนซ์ที่แตกต่างกันผ่านที่อยู่ของพวกเขา (อินสแตนซ์ที่ว่างเปล่าไม่มีอะไรอื่นที่จะแยกแยะได้ ... ) ไม่ว่าในกรณีใด ๆ ความจำเป็นที่จะต้องใส่ใน 'การทำงานร่วมกัน' เค้าโครงแบ่งแพ็ดไบต์
greggo

106

สิ่งที่เปลี่ยนไปสำหรับ C ++ 14

เราสามารถอ้างถึงมาตรฐานฉบับร่าง C ++ 14สำหรับการอ้างอิง

มวลรวม

นี้จะครอบคลุมในส่วน8.5.1 มวลซึ่งทำให้เรามีความหมายต่อไปนี้:

การรวมเป็นอาร์เรย์หรือคลาส (ข้อ 9) ที่ไม่มีตัวสร้างที่ผู้ใช้กำหนด (12.1) ไม่มีสมาชิกข้อมูลส่วนตัวหรือป้องกันไม่คงที่ (ข้อ 11) ไม่มีคลาสพื้นฐาน (ข้อ 10) และไม่มีฟังก์ชั่นเสมือน (10.3) )

การเปลี่ยนแปลงเพียงอย่างเดียวคือการเพิ่มinitializers สมาชิกในคลาสไม่ได้ทำให้คลาสเป็นแบบไม่รวมกัน ตัวอย่างต่อไปนี้จากการเริ่มต้นรวม C ++ 11 สำหรับคลาสที่มี initializers สมาชิกแบบ in-pace :

struct A
{
  int a = 3;
  int b = 3;
};

ไม่ใช่การรวมใน C ++ 11 แต่อยู่ใน C ++ 14 การเปลี่ยนแปลงนี้ครอบคลุมในN3605: สมาชิกผู้เริ่มต้นและมวลรวมซึ่งมีบทคัดย่อดังต่อไปนี้:

Bjarne Stroustrup และ Richard Smith หยิบยกประเด็นเกี่ยวกับการเริ่มต้นรวมและสมาชิก initializers ไม่ทำงานร่วมกัน บทความนี้เสนอที่จะแก้ไขปัญหาโดยการนำถ้อยคำที่สมิ ธ นำเสนอซึ่งเอาข้อ จำกัด ที่การรวมไม่สามารถมีสมาชิกเริ่มต้น

POD ยังคงเหมือนเดิม

คำจำกัดความของโครงสร้าง POD ( ข้อมูลเก่าแบบธรรมดา ) ได้รับการกล่าวถึงในส่วนของ9 คลาสที่ระบุว่า:

POD struct 110เป็นคลาสที่ไม่รวมกันซึ่งเป็นทั้งคลาสที่ไม่สำคัญและคลาสเลย์เอาต์มาตรฐานและไม่มีสมาชิกข้อมูลที่ไม่คงที่ของประเภทที่ไม่ใช่ POD struct, non-POD union (หรืออาเรย์ประเภทดังกล่าว) ในทำนองเดียวกัน POD union เป็นสหภาพที่เป็นทั้งคลาสที่ไม่สำคัญและคลาสเลย์เอาต์มาตรฐานและไม่มีสมาชิกข้อมูลที่ไม่ใช่แบบคงที่ที่ไม่ใช่ POD struct, non-POD union (หรืออาเรย์ประเภทดังกล่าว) คลาส POD เป็นคลาสที่เป็น POD struct หรือ POD union

ซึ่งเป็นถ้อยคำเดียวกันกับ C ++ 11

การเปลี่ยนแปลงเค้าโครงมาตรฐานสำหรับ C ++ 14

ดังที่ระบุไว้ในความคิดเห็นของพ็อดจะขึ้นอยู่กับคำจำกัดความของเลย์เอาต์มาตรฐานและที่เปลี่ยนแปลงสำหรับ C ++ 14 แต่ผ่านการรายงานข้อบกพร่องที่ใช้กับ C ++ 14 หลังจากข้อเท็จจริง

มีสาม DRs:

เลย์เอาต์มาตรฐานจึงมาจาก Pre C ++ 14:

คลาสเลย์เอาต์มาตรฐานคือคลาสที่:

  • (7.1) ไม่มีสมาชิกข้อมูลที่ไม่คงที่ประเภทคลาสที่ไม่เป็นมาตรฐาน (หรืออาเรย์ประเภทดังกล่าว) หรือการอ้างอิง
  • (7.2) ไม่มีฟังก์ชั่นเสมือน ([class.virtual]) และไม่มีคลาสฐานเสมือน ([class.mi])
  • (7.3) มีการควบคุมการเข้าถึงเดียวกัน (ข้อ [class.access]) สำหรับสมาชิกข้อมูลที่ไม่คงที่ทั้งหมด
  • (7.4) ไม่มีคลาสพื้นฐานที่ไม่มีเลย์เอาต์
  • (7.5) อาจไม่มีสมาชิกข้อมูลที่ไม่คงที่ในคลาสที่ได้รับมามากที่สุดและคลาสพื้นฐานส่วนใหญ่ที่มีสมาชิกข้อมูลที่ไม่คงที่หรือไม่มีคลาสฐานที่มีสมาชิกข้อมูลที่ไม่คงที่และ
  • (7.6) ไม่มีคลาสพื้นฐานที่เป็นชนิดเดียวกันกับข้อมูลที่ไม่คงที่สมาชิกแรก 106

ถึงสิ่งนี้ใน C ++ 14 :

คลาส S เป็นคลาสเลย์เอาต์มาตรฐานหาก:

  • (3.1) ไม่มีสมาชิกข้อมูลที่ไม่คงที่ของประเภทที่ไม่ได้มาตรฐานระดับเค้าโครง (หรืออาร์เรย์ของประเภทดังกล่าว) หรือการอ้างอิง
  • (3.2) ไม่มีฟังก์ชั่นเสมือนจริงและไม่มีคลาสฐานเสมือน
  • (3.3) มีการควบคุมการเข้าถึงเดียวกันสำหรับสมาชิกข้อมูลที่ไม่คงที่ทั้งหมด
  • (3.4) ไม่มีคลาสพื้นฐานที่ไม่มีเลย์เอาต์
  • (3.5) มี subobject คลาสพื้นฐานอย่างน้อยหนึ่งประเภทที่กำหนด
  • (3.6) มีสมาชิกข้อมูลที่ไม่คงที่และบิตฟิลด์ในคลาสและคลาสพื้นฐานที่ประกาศครั้งแรกในคลาสเดียวกันและ
  • (3.7) ไม่มีองค์ประกอบของชุด M (S) ประเภทเป็นคลาสพื้นฐานโดยที่ประเภท X, M (X) ใด ๆ จะถูกกำหนดดังต่อไปนี้ 104 [หมายเหตุ: M (X) คือชุดของประเภทของ subobjects ที่ไม่ใช่ระดับพื้นฐานทั้งหมดที่อาจจะชดเชยที่ศูนย์ใน X - หมายเหตุท้าย]
    • (3.7.1) ถ้า X เป็นประเภทคลาสที่ไม่ใช่สหภาพโดยไม่มีสมาชิก (ไม่ใช่หรือสืบทอดมาจาก) ข้อมูลคงที่ชุด M (X) จะว่างเปล่า
    • (3.7.2) ถ้า X เป็นประเภทที่ไม่รวมสหภาพที่มีสมาชิกข้อมูลไม่คงที่ของประเภท X0 ที่มีขนาดเป็นศูนย์หรือเป็นสมาชิกข้อมูลที่ไม่คงที่ครั้งแรกของ X (ที่สมาชิกดังกล่าวอาจเป็นสหภาพที่ไม่ระบุชื่อ ) ชุด M (X) ประกอบด้วย X0 และองค์ประกอบของ M (X0)
    • (3.7.3) ถ้า X เป็นประเภทยูเนี่ยนชุด M (X) คือการรวมกันของ M (Ui) และชุดที่มี UI ทั้งหมดโดยที่ UI แต่ละประเภทเป็นสมาชิกของข้อมูลที่ไม่คงที่ของ X .
    • (3.7.4) ถ้า X เป็นประเภทอาร์เรย์ที่มีประเภทองค์ประกอบ Xe ชุด M (X) ประกอบด้วย Xe และองค์ประกอบของ M (Xe)
    • (3.7.5) ถ้า X เป็นประเภทที่ไม่ใช่คลาส, ไม่ใช่อาร์เรย์ชุด M (X) จะว่างเปล่า

4
มีข้อเสนอเพื่ออนุญาตให้มวลรวมมีคลาสพื้นฐานตราบใดที่มันเป็นค่าเริ่มต้นที่สามารถสร้างได้ดูN4404
Shafik Yaghmour

ในขณะที่ POD อาจอยู่เหมือนกัน, C ++ 14 StandardLayoutType ซึ่งเป็นข้อกำหนดสำหรับ POD ได้เปลี่ยนไปตาม cppref: en.cppreference.com/w/cpp/named_req/StandardLayoutType
Ciro Santilli郝海东冠状病六四事件法轮功

1
@CiroSantilli 新疆改造中心六四事件法轮功ขอบคุณฉันไม่ทราบว่าฉันพลาดได้อย่างไรฉันจะพยายามอัปเดตในอีกสองสามวันถัดไป
Shafik Yaghmour

แจ้งให้เราทราบหากคุณสามารถหาตัวอย่างที่เป็น POD ใน C ++ 14 แต่ไม่ใช่ใน C ++ 11 :-) ฉันได้เริ่มรายการตัวอย่างโดยละเอียดที่: stackoverflow.com/questions/146452/what- are-pod-types-in-c / …
Ciro Santilli 法轮功冠状病六四事件法轮功

1
@CiroSantilli 新疆改造中心六四事件法轮功ดังนั้นสิ่งที่เกิดขึ้นที่นี่คือถ้าเราดูคำอธิบายเค้าโครงมาตรฐานในC ++ 11และC ++ 14ตรงกัน การเปลี่ยนแปลงเหล่านี้เมื่อนำไปใช้ผ่านรายงานข้อบกพร่องกลับไปที่ C ++ 14 ดังนั้นเมื่อฉันเขียนสิ่งนี้มันถูกต้องแล้ว :-p
Shafik Yaghmour

47

คุณช่วยอธิบายเพิ่มเติมกฎต่อไปนี้ได้ไหม:

ฉันจะพยายาม:

a) คลาสเลย์เอาต์มาตรฐานต้องมีสมาชิกข้อมูลที่ไม่คงที่ทั้งหมดที่มีการควบคุมการเข้าถึงเดียวกัน

นั่นเป็นเรื่องง่าย: ทั้งหมดที่ไม่ใช่สมาชิกข้อมูลคงต้องทั้งหมดจะเป็นpublic, หรือprivate protectedคุณไม่สามารถมีบางส่วนและบางส่วนpublicprivate

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

ย้อนกลับไปเมื่อพวกเขาสร้างมาตรฐาน C ++ ใน 98 พวกเขาต้องคาดเดาโดยทั่วไปว่าผู้คนจะนำไปปฏิบัติอย่างไร ในขณะที่พวกเขามีประสบการณ์การใช้งานที่ค่อนข้างหลากหลายด้วยรสชาติของ C ++ พวกเขาไม่แน่ใจเกี่ยวกับสิ่งต่าง ๆ ดังนั้นพวกเขาจึงตัดสินใจที่จะระแวดระวัง: ให้คอมไพเลอร์ให้อิสระมากที่สุด

นั่นเป็นเหตุผลที่คำจำกัดความของ POD ใน C ++ 98 นั้นเข้มงวดมาก มันให้คอมไพเลอร์ C ++ กับละติจูดที่ยอดเยี่ยมสำหรับโครงร่างของสมาชิกสำหรับคลาสส่วนใหญ่ โดยพื้นฐานแล้วประเภทของ POD มีวัตถุประสงค์เพื่อเป็นกรณีพิเศษสิ่งที่คุณเขียนโดยเฉพาะสำหรับเหตุผล

เมื่อ C ++ 11 ทำงานอยู่พวกเขามีประสบการณ์มากขึ้นกับคอมไพเลอร์ และพวกเขารู้ว่า ... นักเขียนคอมไพเลอร์ C ++ ขี้เกียจจริงๆ พวกเขามีอิสระทั้งหมดนี้ แต่ไม่มีทำอะไรกับมัน

กฎของเลย์เอาต์มาตรฐานนั้นมีการประมวลผลร่วมกันมากขึ้นหรือน้อยลงคอมไพเลอร์ส่วนใหญ่ไม่จำเป็นต้องเปลี่ยนแปลงมากนักหากมีอะไรที่จะนำไปใช้จริง

ตอนนี้เมื่อมันมาถึงpublic/ privateสิ่งต่าง ๆ อิสระในการเรียงลำดับสมาชิกใหม่publicกับprivateจริงสามารถสำคัญกับคอมไพเลอร์โดยเฉพาะอย่างยิ่งในการแก้จุดบกพร่องสร้าง และเนื่องจากจุดของเลย์เอาต์มาตรฐานคือว่ามีความเข้ากันได้กับภาษาอื่นคุณจึงไม่สามารถมีเลย์เอาต์ที่แตกต่างกันในการดีบักกับการเปิดตัว

มีความจริงที่ว่ามันไม่ได้ทำให้ผู้ใช้เจ็บจริง ๆ หากคุณกำลังสร้างคลาส encapsulated โอกาสดีที่สมาชิกข้อมูลทั้งหมดของคุณจะเป็นprivateอย่างไร โดยทั่วไปคุณจะไม่เปิดเผยข้อมูลสมาชิกสาธารณะในประเภทที่ห่อหุ้มอย่างสมบูรณ์ ดังนั้นนี่จะเป็นปัญหาสำหรับผู้ใช้ไม่กี่คนที่ต้องการทำเช่นนั้นซึ่งต้องการแบ่งส่วนนั้น

ดังนั้นจึงไม่มีการสูญเสียครั้งใหญ่

b) คลาสเดียวเท่านั้นในแผนผังการสืบทอดทั้งหมดสามารถมีสมาชิกข้อมูลไม่คงที่

เหตุผลสำหรับสิ่งนี้กลับมาทำไมพวกเขามาตรฐานรูปแบบมาตรฐานอีกครั้ง: การปฏิบัติทั่วไป

มีไม่มีการปฏิบัติร่วมกันเมื่อมันมาถึงการมีสองสมาชิกของต้นไม้มรดกที่จริงเก็บสิ่งที่ บางคนใส่คลาสฐานก่อนที่จะได้รับบางคนทำอย่างอื่น คุณสั่งสมาชิกแบบไหนถ้าพวกเขามาจากคลาสเบสสองคลาส? และอื่น ๆ ผู้เรียบเรียงแตกต่างอย่างมากกับคำถามเหล่านี้

นอกจากนี้ต้องขอบคุณกฎศูนย์ / หนึ่ง / อินฟินิตี้เมื่อคุณบอกว่าคุณสามารถมีสมาชิกได้สองคลาสคุณสามารถพูดได้มากเท่าที่คุณต้องการ สิ่งนี้ต้องการเพิ่มกฎโครงร่างจำนวนมากสำหรับวิธีจัดการกับสิ่งนี้ คุณต้องบอกว่าการสืบทอดหลายอันทำงานอย่างไร, คลาสใดวางข้อมูลก่อนคลาสอื่น ๆ , ซึ่งเป็นกฎจำนวนมาก, เพื่อให้ได้วัสดุน้อยมาก

คุณไม่สามารถสร้างทุกสิ่งที่ไม่มีฟังก์ชั่นเสมือนและเค้าโครงมาตรฐานตัวสร้างเริ่มต้น

และสมาชิกข้อมูลที่ไม่คงที่รายแรกไม่สามารถเป็นประเภทคลาสพื้นฐานได้ (ซึ่งอาจทำลายกฎนามแฝง)

ฉันพูดกับคนนี้ไม่ได้จริงๆ ฉันไม่ได้รับการศึกษามากพอในกฎนามแฝงของ C ++ ที่จะเข้าใจจริงๆ แต่มันมีบางอย่างเกี่ยวกับความจริงที่ว่าสมาชิกฐานจะแชร์ที่อยู่เดียวกับคลาสพื้นฐานนั้น นั่นคือ:

struct Base {};
struct Derived : Base { Base b; };

Derived d;
static_cast<Base*>(&d) == &d.b;

และอาจขัดกับกฎนามแฝงของ C ++ อย่างใด

แต่พิจารณานี้มีประโยชน์อย่างไรจะมีความสามารถในการทำเช่นนี้เคยจริงจะเป็นอย่างไร เนื่องจากคลาสเพียงคลาสเดียวเท่านั้นที่สามารถมีสมาชิกข้อมูลที่ไม่คงที่ดังนั้นจึงDerivedต้องเป็นคลาสนั้น (เนื่องจากBaseเป็นสมาชิก) ดังนั้นBase จะต้องว่างเปล่า (จากข้อมูล) และถ้าBaseว่างเปล่าเช่นเดียวกับคลาสพื้นฐาน ... ทำไมต้องมีข้อมูลสมาชิกเลย

เนื่องจากBaseว่างเปล่าจึงไม่มีสถานะ ดังนั้นฟังก์ชั่นสมาชิกไม่คงที่จะทำสิ่งที่พวกเขาทำขึ้นอยู่กับพารามิเตอร์ของพวกเขาไม่ใช่thisตัวชี้ของพวกเขา

ดังนั้นอีกครั้ง: ไม่มีการสูญเสียครั้งใหญ่


ขอบคุณสำหรับคำอธิบายมันช่วยได้มาก อาจเป็นstatic_cast<Base*>(&d)และ&d.bเป็นBase*ประเภทเดียวกันพวกเขาชี้ไปที่สิ่งต่าง ๆ ดังนั้นจึงเป็นการละเมิดกฎนามแฝง โปรดแก้ไขฉัน
Andriy Tylychko

1
และทำไมถ้ามีเพียงคลาสเดียวเท่านั้นที่สามารถมีสมาชิกข้อมูลที่ไม่คงที่ได้ดังนั้นDerivedต้องเป็นคลาสนั้น
Andriy Tylychko

3
@AndyT: เพื่อให้Derivedเป็นสมาชิกคนแรกจะเป็นชั้นฐานของมันก็ต้องมีสองสิ่ง: ชั้นฐานและสมาชิก และเนื่องจากคลาสเพียงหนึ่งคลาสในลำดับชั้นสามารถมีสมาชิก (และยังคงเป็นเลย์เอาต์มาตรฐาน) ซึ่งหมายความว่าคลาสพื้นฐานของคลาสนั้นจะไม่มีสมาชิก
Nicol Bolas

3
@AndyT ใช่แล้วคุณต้องถูกหลัก IME เกี่ยวกับกฎนามแฝง อินสแตนซ์ที่แตกต่างกันสองประเภทที่เหมือนกันจะต้องมีที่อยู่หน่วยความจำที่แตกต่างกัน (สิ่งนี้ช่วยให้การติดตามตัวตนของวัตถุด้วยที่อยู่หน่วยความจำ) วัตถุฐานและสมาชิกที่ได้รับครั้งแรกเป็นอินสแตนซ์ที่แตกต่างกันดังนั้นพวกเขาจะต้องมีที่อยู่ที่แตกต่างกัน หากเป็นประเภทที่แตกต่างกันก็ไม่เป็นไร วัตถุที่มีประเภทแตกต่างกันได้รับอนุญาตให้มีที่อยู่เดียวกัน (ตัวอย่างเช่นคลาสและสมาชิกข้อมูลแรก)
Adam H. Peterson

46

การเปลี่ยนแปลงใน C ++ 17

ดาวน์โหลด C ++ 17 มาตรฐานสากลร่างสุดท้ายที่นี่

มวลรวม

C ++ 17 ขยายและปรับปรุงการรวมและการเริ่มต้นรวม ขณะนี้ไลบรารีมาตรฐานยังมีstd::is_aggregateคลาสลักษณะพิเศษด้วย นี่คือคำจำกัดความที่เป็นทางการจากหัวข้อ 11.6.1.1 และ 11.6.1.2 (การอ้างอิงภายในช่วย):

การรวมเป็นอาร์เรย์หรือคลาสที่
ไม่มีคอนสตรัคเตอร์ที่ผู้ใช้ระบุหรือสืบทอดโดย
ไม่มีสมาชิกข้อมูลส่วนตัวหรือการป้องกันที่ไม่มีการป้องกัน
ไม่มีฟังก์ชันเสมือนและ
ไม่มีคลาสฐานเสมือนหรือส่วนตัวที่มีการป้องกัน
[หมายเหตุ: การเริ่มต้นรวมไม่อนุญาตให้เข้าถึงสมาชิกหรือคลาสก่อสร้างที่ได้รับการป้องกันและเป็นส่วนตัว - บันทึกย่อ]
องค์ประกอบของการรวมคือ:
- สำหรับอาร์เรย์, องค์ประกอบอาร์เรย์ในการเพิ่มคำสั่งห้อยหรือ
- สำหรับคลาส, คลาสฐานโดยตรงในลำดับการประกาศตามด้วยสมาชิกข้อมูลไม่คงที่โดยตรงที่ไม่ได้ สมาชิกของสหภาพที่ไม่ระบุชื่อตามลำดับการประกาศ

มีอะไรเปลี่ยนแปลง

  1. ตอนนี้การรวมสามารถมีคลาสพื้นฐานที่ไม่ใช่แบบเสมือน นอกจากนี้มันไม่ได้เป็นความต้องการที่คลาสพื้นฐานเป็นมวลรวม หากพวกเขาไม่ได้รวมพวกเขาจะเริ่มต้นรายการ
struct B1 // not a aggregate
{
    int i1;
    B1(int a) : i1(a) { }
};
struct B2
{
    int i2;
    B2() = default;
};
struct M // not an aggregate
{
    int m;
    M(int a) : m(a) { }
};
struct C : B1, B2
{
    int j;
    M m;
    C() = default;
};
C c { { 1 }, { 2 }, 3, { 4 } };
cout
    << "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. ตัวสร้างเริ่มต้นอย่างชัดเจนจะไม่ได้รับอนุญาต
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. สิ่งก่อสร้างที่สืบทอดไม่ได้รับอนุญาต
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


คลาสเล็กน้อย

คำจำกัดความของคลาส trivial ได้รับการทำใหม่ใน C ++ 17 เพื่อแก้ไขข้อบกพร่องต่าง ๆ ที่ไม่ได้ระบุไว้ใน C ++ 14 การเปลี่ยนแปลงเป็นเรื่องทางเทคนิค นี่คือคำจำกัดความใหม่ที่ 12.0.6 (การอ้างอิงภายในช่วย):

คลาส
ที่คัดลอกได้เล็กน้อยคือคลาส: - โดยที่ตัวสร้างสำเนาแต่ละตัวย้ายตัวสร้างตัวดำเนินการกำหนดค่าการคัดลอกและตัวดำเนินการกำหนดค่าการย้ายอาจถูกลบหรือไม่สำคัญ
- ที่มีตัวสร้างสำเนาอย่างน้อยหนึ่งรายการที่ไม่ถูกลบ หรือย้ายโอเปอเรเตอร์การมอบหมายและ
- ซึ่งมี destructor เล็กน้อยที่ไม่ถูกลบ
คลาสเล็ก ๆ น้อย ๆ เป็นคลาสที่สามารถคัดลอกได้เล็กน้อยและมีคอนสตรัคเตอร์เริ่มต้นตั้งแต่หนึ่งตัวขึ้นไปซึ่งทั้งหมดนี้เป็นเพียงเล็กน้อยหรือถูกลบและอย่างน้อยหนึ่งคลาสจะไม่ถูกลบ [หมายเหตุ: โดยเฉพาะอย่างยิ่งคลาสที่คัดลอกได้หรือไม่สำคัญไม่ได้มีฟังก์ชั่นเสมือนหรือคลาสฐานเสมือน - หมายเหตุท้าย]

การเปลี่ยนแปลง:

  1. ภายใต้ C ++ 14 สำหรับคลาสที่ไม่สำคัญคลาสไม่สามารถมีตัวดำเนินการคัดลอก / ย้ายคอนสตรัคเตอร์ / การมอบหมายที่ไม่สำคัญ อย่างไรก็ตามการประกาศโดยปริยายว่าเป็นตัวสร้าง / ตัวดำเนินการเริ่มต้นอาจไม่ใช่เรื่องไม่สำคัญและยังถูกกำหนดเป็นลบเพราะตัวอย่างเช่นคลาสมี subobject ของประเภทคลาสที่ไม่สามารถคัดลอก / ย้ายได้ การปรากฏตัวของคอนสตรัคเตอร์ / โอเปอเรเตอร์ที่ไม่สำคัญดังกล่าวจะถูกกำหนดเป็นลบจะทำให้ทั้งคลาสนั้นไม่สำคัญ ปัญหาที่คล้ายกันมีอยู่กับ destructors C ++ 17 ชี้แจงว่าการมีอยู่ของคอนสตรัคเตอร์ / โอเปอเรเตอร์นั้นไม่ทำให้คลาสไม่สามารถคัดลอกได้เล็กน้อยดังนั้นจึงไม่ใช่เรื่องไม่สำคัญและคลาสที่คัดลอกได้จะต้องมี destructor เล็กน้อย DR1734 , DR1928
  2. C ++ 14 อนุญาตให้คลาสที่คัดลอกได้เล็กน้อยซึ่งเป็นคลาสที่ไม่สำคัญเพื่อให้ผู้ประกอบการคัดลอก / ย้ายคอนสตรัคเตอร์ / ผู้กำหนดที่ได้รับการประกาศว่าถูกลบ ถ้าเช่นชั้นก็ยังเป็นรูปแบบมาตรฐานก็อาจอย่างไรจะถูกต้องตามกฎหมายคัดลอก / std::memcpyย้ายไปอยู่กับ นี่เป็นความหมายที่ขัดแย้งกันเนื่องจากการนิยามว่าเป็นการลบตัวดำเนินการคอนสตรัคเตอร์ / ผู้มอบหมายทั้งหมดผู้สร้างชั้นเรียนตั้งใจอย่างชัดเจนว่าคลาสไม่สามารถคัดลอก / ย้ายได้ แต่คลาสก็ยังคงพบคำจำกัดความของคลาส ดังนั้นใน C ++ 17 เรามีประโยคใหม่ที่ระบุว่าคลาสที่คัดลอกได้เล็กน้อยจะต้องมีอย่างน้อยหนึ่งอย่างน้อยที่ไม่ถูกลบ ดูN4148 , DR1734
  3. การเปลี่ยนแปลงทางเทคนิคที่สามเกี่ยวข้องกับปัญหาที่คล้ายกันกับตัวสร้างเริ่มต้น ภายใต้ C ++ 14 คลาสอาจมีตัวสร้างปริยายเล็กน้อยซึ่งถูกนิยามโดยนัยว่าถูกลบ แต่ยังคงเป็นคลาสที่ไม่สำคัญ คำจำกัดความใหม่ชี้แจงว่าคลาส trivial ต้องมีคอนสตรัคค่าเริ่มต้นเล็กน้อยและไม่ถูกลบอย่างน้อยหนึ่งคลาส ดูDR1496

คลาสเลย์เอาต์มาตรฐาน

คำจำกัดความของเลย์เอาต์มาตรฐานได้รับการทำใหม่เพื่อแก้ไขรายงานข้อบกพร่อง การเปลี่ยนแปลงอีกครั้งเป็นเทคนิคในลักษณะ นี่คือข้อความจากมาตรฐาน (12.0.7) เมื่อก่อนการอ้างอิงภายในจะถูกลบออก:

คลาส S เป็นคลาสเลย์เอาต์มาตรฐานหาก:
- ไม่มีสมาชิกข้อมูลที่ไม่คงที่ของคลาสที่ไม่ได้มาตรฐาน (หรืออาเรย์ประเภทดังกล่าว) หรือการอ้างอิง
- ไม่มีฟังก์ชั่นเสมือนจริงและไม่มีคลาสฐานเสมือน
- มีการควบคุมการเข้าใช้งานเหมือนกันสำหรับสมาชิกข้อมูลที่ไม่คงที่ทั้งหมด
- ไม่มีคลาสพื้นฐานที่ไม่มีรูปแบบมาตรฐาน
- มี subobject คลาสพื้นฐานอย่างน้อยหนึ่งประเภทที่กำหนดประเภทใด ๆ
- มีสมาชิกข้อมูลที่ไม่คงที่และบิตฟิลด์ใน คลาสและคลาสพื้นฐานที่ประกาศครั้งแรกในคลาสเดียวกันและ
- ไม่มีองค์ประกอบของชุด M (S) ของประเภท (กำหนดไว้ด้านล่าง) เป็นคลาสพื้นฐาน 106 - ถ้า X เป็นประเภทคลาสที่ไม่ใช่สหภาพที่ไม่มี ( อาจมีการสืบทอด) สมาชิกข้อมูลที่ไม่คงที่ชุด M (X) ว่างเปล่า
M (X) ถูกกำหนดดังนี้:

- หาก X เป็นประเภทคลาสที่ไม่ใช่สหภาพซึ่งสมาชิกข้อมูลที่ไม่คงที่ครั้งแรกมีประเภท X0 (โดยที่สมาชิกดังกล่าวอาจเป็นสหภาพแบบไม่ระบุชื่อ) ชุด M (X) ประกอบด้วย X0 และองค์ประกอบของ M (X0)
- หาก X เป็นประเภทยูเนี่ยนชุด M (X) คือการรวมกันของ M ทั้งหมด (Ui) และชุดที่มี UI ทั้งหมดโดยที่ UI แต่ละประเภทเป็นสมาชิกข้อมูลที่ไม่ใช่แบบคงที่ของ X
- ถ้า X เป็นประเภทอาร์เรย์ที่มีประเภทองค์ประกอบ Xe ชุด M (X) ประกอบด้วย Xe และองค์ประกอบของ M (Xe) [หมายเหตุ: M (X) คือชุดของประเภทของ subobjects ที่ไม่ใช่ระดับฐานทั้งหมดที่รับประกันในคลาสเลย์เอาต์มาตรฐานเพื่อให้มีออฟเซ็ตเป็นศูนย์ใน X - บันทึกย่อ] [ตัวอย่าง:
- หาก X เป็นประเภทที่ไม่ใช่คลาสและไม่ใช่อาร์เรย์ชุด M (X) จะว่างเปล่า


struct B { int i; }; // standard-layout class
struct C : B { }; // standard-layout class
struct D : C { }; // standard-layout class
struct E : D { char : 4; }; // not a standard-layout class
struct Q {};
struct S : Q { };
struct T : Q { };
struct U : S, T { }; // not a standard-layout class
- ส่งตัวอย่าง]
108) สิ่งนี้ทำให้แน่ใจได้ว่าสอง subobjects ที่มีประเภทคลาสเดียวกันและที่เป็นของวัตถุที่ได้รับมาส่วนใหญ่เหมือนกันจะไม่ได้รับการจัดสรรตามที่อยู่เดียวกัน

การเปลี่ยนแปลง:

  1. ชี้แจงว่าข้อกำหนดที่มีเพียงคลาสเดียวในต้นไม้ที่สืบทอดมา "มี" สมาชิกข้อมูลที่ไม่คงที่หมายถึงคลาสที่สมาชิกข้อมูลดังกล่าวถูกประกาศครั้งแรกไม่ใช่คลาสที่พวกเขาอาจได้รับการสืบทอดและขยายความต้องการนี้ไปยังฟิลด์บิตที่ไม่คงที่ . ชี้แจงด้วยว่าคลาสเลย์เอาต์มาตรฐาน "มี subobject คลาสพื้นฐานอย่างน้อยหนึ่งประเภทที่กำหนด" ดูDR1813 , DR1881
  2. คำจำกัดความของเลย์เอาต์มาตรฐานไม่อนุญาตให้ชนิดของคลาสพื้นฐานใด ๆ เป็นชนิดเดียวกับสมาชิกข้อมูลแบบไม่คงที่ครั้งแรก มันคือการหลีกเลี่ยงสถานการณ์ที่สมาชิกข้อมูลที่ offset zero มีประเภทเดียวกันกับคลาสพื้นฐานใด ๆ มาตรฐาน C ++ 17 ให้คำจำกัดความที่ซ้ำซ้อนและเข้มงวดยิ่งขึ้นของ "ชุดประเภทของ subobjects ที่ไม่ใช่ระดับฐานทั้งหมดที่รับประกันในคลาสเลย์เอาต์มาตรฐานที่จะชดเชยที่ศูนย์" เพื่อห้ามประเภทดังกล่าว จากการเป็นประเภทของชั้นฐานใด ๆ ดูDR1672 , DR2120

หมายเหตุ:คณะกรรมการมาตรฐาน C ++ ตั้งใจเปลี่ยนแปลงดังกล่าวตามรายงานข้อบกพร่องเพื่อใช้กับ C ++ 14 แม้ว่าภาษาใหม่ไม่ได้อยู่ในมาตรฐาน C ++ 14 ที่เผยแพร่ มันอยู่ในมาตรฐาน C ++ 17


หมายเหตุฉันเพิ่งอัปเดตคำตอบข้อบกพร่องการเปลี่ยนแปลงเลย์เอาต์มาตรฐานมีสถานะ CD4ซึ่งหมายความว่าพวกเขาจะใช้กับ C ++ 14 จริง ๆ ซึ่งเป็นสาเหตุที่คำตอบของฉันไม่ได้รวมสิ่งเหล่านี้ b / c เกิดขึ้นหลังจากที่ฉันเขียนคำตอบของฉัน
Shafik Yaghmour

หมายเหตุฉันเริ่มให้รางวัลกับคำถามนี้
Shafik Yaghmour

ขอบคุณ @ShafikYaghmour ฉันจะตรวจสอบสถานะรายงานข้อบกพร่องและแก้ไขคำตอบของฉันตามนั้น
ThomasMcLeod

@ShafikYaghmour หลังจากตรวจสอบกระบวนการ C ++ 14 และดูเหมือนว่าในขณะที่ DR เหล่านี้ "ยอมรับ" ในเดือนมิถุนายน 2014 Rapperswil ประชุมภาษาจากการประชุมกุมภาพันธ์ Issaquah คือสิ่งที่กลายเป็น C ++ 14 ดูisocpp.org/blog/2014/07/trip-report-summer-iso-c-meeting "ตามกฎ ISO เราไม่ได้อนุมัติการแก้ไขใด ๆ ในเอกสารการทำงาน C ++ อย่างเป็นทางการ" ฉันพลาดอะไรไปรึเปล่า?
ThomasMcLeod

พวกเขามีสถานะ 'CD4' ซึ่งหมายความว่าควรใช้ในโหมด C ++ 14
Shafik Yaghmour

14

สิ่งที่เปลี่ยนแปลงไป

ตามธีมที่เหลือของคำถามนี้ความหมายและการใช้มวลรวมจะเปลี่ยนไปอย่างต่อเนื่องในทุกมาตรฐาน มีการเปลี่ยนแปลงที่สำคัญหลายอย่างบนขอบฟ้า

ประเภทที่มีคอนสตรัคเตอร์ที่ผู้ใช้ประกาศP1008

ใน C ++ 17 ประเภทนี้ยังคงเป็นการรวม:

struct X {
    X() = delete;
};

และด้วยเหตุนี้X{}ยังคงรวบรวมเพราะนั่นคือการเริ่มต้นรวม - ไม่เรียกใช้ตัวสร้าง ดูเพิ่มเติมที่: คอนสตรัคเตอร์ส่วนตัวไม่ได้เป็นคอนสตรัคเตอร์ส่วนตัวเมื่อใด

ใน C ++ 20 ข้อ จำกัด จะเปลี่ยนจากที่ต้องการ:

ไม่มีการจัดเตรียมโดยผู้ใช้explicitหรือสืบทอดงานก่อสร้าง

ถึง

ไม่มีคอนสตรัคเตอร์ที่ผู้ใช้ประกาศหรือสืบทอด

นี้ได้ถูกนำเข้าไปในร่างการทำงาน C ++ 20 ทั้งที่Xนี่และCในคำถามที่เชื่อมโยงจะถูกรวมใน C ++ 20

สิ่งนี้ยังทำให้เอฟเฟ็กต์ yo-yo ด้วยตัวอย่างต่อไปนี้:

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

ใน C ++ 11/14, Bก็ไม่ได้รวมกันเนื่องจากชั้นฐานเพื่อให้B{}การดำเนินการที่มีมูลค่าเริ่มต้นที่สายB::B()ที่โทรA::A()ในจุดที่จะสามารถเข้าถึงได้ นี่เป็นรูปแบบที่ดี

ใน C ++ 17 Bกลายเป็นผลรวมเนื่องจากคลาสพื้นฐานได้รับอนุญาตซึ่งทำให้B{}การเริ่มต้นรวม เรื่องนี้ต้องมีการคัดลอกรายการเริ่มต้นAจาก{}แต่จากนอกบริบทของการBที่มันไม่สามารถเข้าถึงได้ ใน C ++ 17 นี่เป็นรูปแบบที่ไม่ดี ( auto x = B();น่าจะดีกว่า)

ใน C ++ 20 ในขณะนี้เนื่องจากการเปลี่ยนแปลงกฎข้างต้นBอีกครั้งจะสิ้นสุดสภาพการรวม (ไม่ใช่เพราะคลาสพื้นฐาน แต่เนื่องจากตัวสร้างเริ่มต้นที่ผู้ใช้ประกาศ - แม้ว่าจะเป็นค่าเริ่มต้น) เรากลับไปBที่คอนสตรัคเตอร์และตัวอย่างนี้กลายเป็นรูปแบบที่ดี

การเริ่มต้นการรวมจากรายการที่อยู่ในวงเล็บของค่าP960

ปัญหาทั่วไปที่เกิดขึ้นคือต้องการใช้ตัวemplace()สร้างสไตล์กับมวลรวม:

struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error

สิ่งนี้ไม่ทำงานเนื่องจากemplaceจะพยายามทำการเริ่มต้นอย่างมีประสิทธิภาพX(1, 2)ซึ่งไม่ถูกต้อง วิธีแก้ปัญหาทั่วไปคือการเพิ่มตัวสร้างXแต่ด้วยข้อเสนอนี้ (ปัจจุบันทำงานผ่านแกน) มวลรวมจะได้อย่างมีประสิทธิภาพได้สังเคราะห์ตัวสร้างซึ่งทำในสิ่งที่ถูกต้อง - และทำตัวเหมือนตัวสร้างแบบปกติ รหัสข้างต้นจะรวบรวมตามที่เป็นอยู่ใน C ++ 20

การหักอาร์กิวเมนต์เทมเพลตคลาส (CTAD) สำหรับมวลรวมP1021 (เฉพาะP1816 )

ใน C ++ 17 สิ่งนี้ไม่ได้รวบรวม:

template <typename T>
struct Point {
    T x, y;
};

Point p{1, 2}; // error

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

template <typename T> Point(T, T) -> Point<T>;

แต่เนื่องจากในแง่หนึ่ง "สิ่งที่ชัดเจน" ที่ต้องทำและโดยพื้นฐานแล้วเป็นเพียงแผ่นเหล็กสำเร็จรูปภาษาจะทำสิ่งนี้ให้คุณ ตัวอย่างนี้จะรวบรวมใน C ++ 20 (โดยไม่จำเป็นต้องมีคู่มือการหักเงินที่ผู้ใช้จัดหา)


ถึงแม้ว่าฉันจะอัปโหลดมันรู้สึกว่าเร็ว ๆ นี้ที่จะเพิ่มสิ่งนี้ แต่ฉันไม่ทราบว่ามีอะไรสำคัญที่จะมาลงที่จะเปลี่ยนแปลงสิ่งนี้ก่อนที่จะทำ C ++ 2x
Shafik Yaghmour

@ShafikYaghmour ใช่อาจจะเร็วเกินไป แต่เนื่องจาก SD เป็นกำหนดเวลาสำหรับคุณสมบัติภาษาใหม่สิ่งเหล่านี้เป็นเพียงสองเที่ยวบินที่ฉันรู้ - กรณีที่เลวร้ายที่สุดที่ฉันเพิ่งปิดกั้นลบหนึ่งในส่วนเหล่านี้ในภายหลังหรือไม่ ฉันเพิ่งเห็นคำถามที่ใช้งานกับเงินรางวัลและคิดว่ามันเป็นเวลาที่ดีที่จะพูดสอดในก่อนที่ฉันจะลืม
Barry

ฉันเข้าใจฉันถูกล่อลวงสองสามครั้งสำหรับกรณีที่คล้ายกัน ฉันมักจะกังวลบางสิ่งบางอย่างที่สำคัญจะเปลี่ยนไปและฉันจะต้องเขียนใหม่
Shafik Yaghmour

@ShafikYaghmour ดูเหมือนว่าไม่มีอะไรจะเปลี่ยนที่นี่ :)
แบร์รี่

ฉันหวังว่าสิ่งนี้จะได้รับการอัปเดตตอนนี้ด้วย C ++ 20 ที่เปิดตัวแล้ว
ไม่มีใครเลย
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.