นี้คำถามที่พบบ่อยเป็นเรื่องเกี่ยวกับปริมาณและ PODs และครอบคลุมวัสดุดังต่อไปนี้:
- อะไรคือมวล ?
- อะไรคือPOD s (ข้อมูล Plain Old)?
- พวกเขาเกี่ยวข้องกันอย่างไร
- พวกเขาพิเศษอย่างไรและทำไม
- การเปลี่ยนแปลงสำหรับ C ++ 11 คืออะไร
นี้คำถามที่พบบ่อยเป็นเรื่องเกี่ยวกับปริมาณและ PODs และครอบคลุมวัสดุดังต่อไปนี้:
คำตอบ:
บทความนี้ค่อนข้างยาว หากคุณต้องการทราบเกี่ยวกับทั้งมวลรวมและ 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
สมาชิกคงได้รับการคุ้มครองไม่ได้เริ่มต้นที่ทุกคนเพราะมันเป็นd
static
สหภาพการรวมจะแตกต่างกันซึ่งคุณสามารถเริ่มต้นได้เฉพาะสมาชิกแรกของพวกเขาด้วยเครื่องหมายปีกกา ฉันคิดว่าถ้าคุณมีความก้าวหน้าเพียงพอใน C ++ เพื่อพิจารณาการใช้สหภาพ (การใช้งานของพวกเขาอาจเป็นอันตรายมากและต้องพิจารณาอย่างรอบคอบ) คุณสามารถค้นหากฎสำหรับสหภาพในมาตรฐานของคุณเอง :)
ตอนนี้เรารู้แล้วว่ามีอะไรพิเศษเกี่ยวกับการรวมตัวลองมาทำความเข้าใจข้อ จำกัด ของคลาส นั่นคือเหตุผลที่พวกเขาอยู่ที่นั่น เราควรเข้าใจว่าการกำหนดค่าเริ่มต้นแบบสมาชิกด้วยวงเล็บปีกกาหมายความว่าชั้นเรียนนั้นไม่มีอะไรมากไปกว่าผลรวมของสมาชิก หากมีคอนสตรัคเตอร์ที่ผู้ใช้กำหนดเองแสดงว่าผู้ใช้ต้องทำงานพิเศษบางอย่างเพื่อเริ่มต้นสมาชิกดังนั้นการเตรียมใช้งานวงเล็บปีกกาจะไม่ถูกต้อง หากมีฟังก์ชั่นเสมือนจริงแสดงว่าวัตถุของคลาสนี้มีตัวชี้ไปยัง vtable ที่เรียกว่าคลาสซึ่งตั้งอยู่ในตัวสร้างดังนั้นในวงเล็บจะเริ่มต้นไม่เพียงพอ คุณสามารถหาข้อ จำกัด ที่เหลือในลักษณะเดียวกันกับแบบฝึกหัด :)
เพียงพอเกี่ยวกับมวลรวม ตอนนี้เราสามารถกำหนดชุดประเภทที่เข้มงวดเพื่อปัญญา 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หรือไม่)
ตัวอย่าง:
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 คืออะไรเพราะมีคุณสมบัติทางภาษามากมายดังที่คุณเห็น
private:
ตามความเหมาะสม): struct A { int const a; };
จากนั้นA()
มีรูปแบบที่ดีแม้ว่าA
คำจำกัดความของคอนสตรัคเตอร์เริ่มต้นจะเป็นรูปแบบที่ไม่ดี
คำจำกัดความมาตรฐานของผลรวมมีการเปลี่ยนแปลงเล็กน้อย แต่ก็ยังคงเหมือนเดิม:
การรวมเป็นอาเรย์หรือคลาส (ข้อ 9) โดยไม่มีคอนสตรัคเตอร์ที่ผู้ใช้จัดเตรียมไว้ (12.1), ไม่มีวงเล็บปีกกาหรือเท่ากับค่าเริ่มต้นสำหรับสมาชิกข้อมูลที่ไม่คงที่ (9.2), ไม่มีสมาชิกข้อมูลส่วนตัว ข้อ 11) ไม่มีคลาสพื้นฐาน (ข้อ 10) และไม่มีฟังก์ชั่นเสมือน (10.3)
ตกลงมีอะไรเปลี่ยนแปลงบ้าง
ก่อนหน้านี้รวมกันจะไม่มีใช้ประกาศก่อสร้าง แต่ตอนนี้มันไม่สามารถมีผู้ใช้ให้ก่อสร้าง มีความแตกต่างหรือไม่? ใช่มีเพราะตอนนี้คุณสามารถประกาศตัวสร้างและกำหนดค่าเริ่มต้นได้:
struct Aggregate {
Aggregate() = default; // asks the compiler to generate the default implementation
};
สิ่งนี้ยังคงเป็นการรวมกันเนื่องจากตัวสร้าง (หรือฟังก์ชันสมาชิกพิเศษใด ๆ ) ที่มีค่าเริ่มต้นในการประกาศครั้งแรกนั้นไม่ได้จัดทำโดยผู้ใช้
ตอนนี้การรวมไม่สามารถมีวงเล็บปีกกาหรือเท่ากับ initializersสำหรับสมาชิกข้อมูลที่ไม่คงที่ สิ่งนี้หมายความว่า? นี่เป็นเพียงเพราะมาตรฐานใหม่นี้เราสามารถเริ่มสมาชิกโดยตรงในชั้นเรียนเช่นนี้:
struct NotAggregate {
int x = 5; // valid in C++11
std::vector<int> s{1,2,3}; // also valid
};
การใช้คุณสมบัตินี้ทำให้คลาสไม่รวมกันอีกต่อไปเพราะโดยพื้นฐานแล้วมันจะเทียบเท่ากับการสร้างคอนสตรัคเตอร์เริ่มต้นของคุณเอง
ดังนั้นสิ่งที่รวมกันไม่ได้เปลี่ยนแปลงอะไรมากมายเลย มันยังคงเป็นแนวคิดพื้นฐานเดียวกันปรับให้เข้ากับคุณสมบัติใหม่
POD ผ่านการเปลี่ยนแปลงมากมาย กฎก่อนหน้าจำนวนมากเกี่ยวกับ POD ได้ผ่อนคลายในมาตรฐานใหม่นี้และวิธีการให้คำจำกัดความในมาตรฐานนั้นเปลี่ยนไปอย่างสิ้นเชิง
แนวคิดของ POD คือการจับคุณสมบัติที่แตกต่างกันสองประการ:
ด้วยเหตุนี้คำจำกัดความจึงถูกแยกออกเป็นสองแนวคิดที่แตกต่าง: คลาสที่ไม่สำคัญและคลาสเลย์เอาต์มาตรฐานเนื่องจากสิ่งเหล่านี้มีประโยชน์มากกว่า 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;
เราสามารถอ้างถึงมาตรฐานฉบับร่าง 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 ( ข้อมูลเก่าแบบธรรมดา ) ได้รับการกล่าวถึงในส่วนของ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 หลังจากข้อเท็จจริง
มีสาม 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) จะว่างเปล่า
คุณช่วยอธิบายเพิ่มเติมกฎต่อไปนี้ได้ไหม:
ฉันจะพยายาม:
a) คลาสเลย์เอาต์มาตรฐานต้องมีสมาชิกข้อมูลที่ไม่คงที่ทั้งหมดที่มีการควบคุมการเข้าถึงเดียวกัน
นั่นเป็นเรื่องง่าย: ทั้งหมดที่ไม่ใช่สมาชิกข้อมูลคงต้องทั้งหมดจะเป็นpublic
, หรือprivate
protected
คุณไม่สามารถมีบางส่วนและบางส่วนpublic
private
เหตุผลสำหรับพวกเขาไปที่การให้เหตุผลที่มีความแตกต่างระหว่าง "รูปแบบมาตรฐาน" และ "ไม่ใช่รูปแบบมาตรฐาน" เลย คือเพื่อให้คอมไพเลอร์มีอิสระในการเลือกวิธีการใส่สิ่งต่าง ๆ ในความทรงจำ มันไม่ได้เป็นเพียงตัวชี้ 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*
ประเภทเดียวกันพวกเขาชี้ไปที่สิ่งต่าง ๆ ดังนั้นจึงเป็นการละเมิดกฎนามแฝง โปรดแก้ไขฉัน
Derived
ต้องเป็นคลาสนั้น
Derived
เป็นสมาชิกคนแรกจะเป็นชั้นฐานของมันก็ต้องมีสองสิ่ง: ชั้นฐานและสมาชิก และเนื่องจากคลาสเพียงหนึ่งคลาสในลำดับชั้นสามารถมีสมาชิก (และยังคงเป็นเลย์เอาต์มาตรฐาน) ซึ่งหมายความว่าคลาสพื้นฐานของคลาสนั้นจะไม่มีสมาชิก
ดาวน์โหลด C ++ 17 มาตรฐานสากลร่างสุดท้ายที่นี่
มวลรวม
C ++ 17 ขยายและปรับปรุงการรวมและการเริ่มต้นรวม ขณะนี้ไลบรารีมาตรฐานยังมีstd::is_aggregate
คลาสลักษณะพิเศษด้วย นี่คือคำจำกัดความที่เป็นทางการจากหัวข้อ 11.6.1.1 และ 11.6.1.2 (การอ้างอิงภายในช่วย):
การรวมเป็นอาร์เรย์หรือคลาสที่
ไม่มีคอนสตรัคเตอร์ที่ผู้ใช้ระบุหรือสืบทอดโดย
ไม่มีสมาชิกข้อมูลส่วนตัวหรือการป้องกันที่ไม่มีการป้องกัน
ไม่มีฟังก์ชันเสมือนและ
ไม่มีคลาสฐานเสมือนหรือส่วนตัวที่มีการป้องกัน
[หมายเหตุ: การเริ่มต้นรวมไม่อนุญาตให้เข้าถึงสมาชิกหรือคลาสก่อสร้างที่ได้รับการป้องกันและเป็นส่วนตัว - บันทึกย่อ]
องค์ประกอบของการรวมคือ:
- สำหรับอาร์เรย์, องค์ประกอบอาร์เรย์ในการเพิ่มคำสั่งห้อยหรือ
- สำหรับคลาส, คลาสฐานโดยตรงในลำดับการประกาศตามด้วยสมาชิกข้อมูลไม่คงที่โดยตรงที่ไม่ได้ สมาชิกของสหภาพที่ไม่ระบุชื่อตามลำดับการประกาศ
มีอะไรเปลี่ยนแปลง
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
struct D // not an aggregate
{
int i = 0;
D() = default;
explicit D(D const&) = default;
};
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 เล็กน้อยที่ไม่ถูกลบ
คลาสเล็ก ๆ น้อย ๆ เป็นคลาสที่สามารถคัดลอกได้เล็กน้อยและมีคอนสตรัคเตอร์เริ่มต้นตั้งแต่หนึ่งตัวขึ้นไปซึ่งทั้งหมดนี้เป็นเพียงเล็กน้อยหรือถูกลบและอย่างน้อยหนึ่งคลาสจะไม่ถูกลบ [หมายเหตุ: โดยเฉพาะอย่างยิ่งคลาสที่คัดลอกได้หรือไม่สำคัญไม่ได้มีฟังก์ชั่นเสมือนหรือคลาสฐานเสมือน - หมายเหตุท้าย]
การเปลี่ยนแปลง:
std::memcpy
ย้ายไปอยู่กับ นี่เป็นความหมายที่ขัดแย้งกันเนื่องจากการนิยามว่าเป็นการลบตัวดำเนินการคอนสตรัคเตอร์ / ผู้มอบหมายทั้งหมดผู้สร้างชั้นเรียนตั้งใจอย่างชัดเจนว่าคลาสไม่สามารถคัดลอก / ย้ายได้ แต่คลาสก็ยังคงพบคำจำกัดความของคลาส ดังนั้นใน C ++ 17 เรามีประโยคใหม่ที่ระบุว่าคลาสที่คัดลอกได้เล็กน้อยจะต้องมีอย่างน้อยหนึ่งอย่างน้อยที่ไม่ถูกลบ ดูN4148 , DR1734คลาสเลย์เอาต์มาตรฐาน
คำจำกัดความของเลย์เอาต์มาตรฐานได้รับการทำใหม่เพื่อแก้ไขรายงานข้อบกพร่อง การเปลี่ยนแปลงอีกครั้งเป็นเทคนิคในลักษณะ นี่คือข้อความจากมาตรฐาน (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 ที่มีประเภทคลาสเดียวกันและที่เป็นของวัตถุที่ได้รับมาส่วนใหญ่เหมือนกันจะไม่ได้รับการจัดสรรตามที่อยู่เดียวกัน
การเปลี่ยนแปลง:
หมายเหตุ:คณะกรรมการมาตรฐาน C ++ ตั้งใจเปลี่ยนแปลงดังกล่าวตามรายงานข้อบกพร่องเพื่อใช้กับ C ++ 14 แม้ว่าภาษาใหม่ไม่ได้อยู่ในมาตรฐาน C ++ 14 ที่เผยแพร่ มันอยู่ในมาตรฐาน C ++ 17
ตามธีมที่เหลือของคำถามนี้ความหมายและการใช้มวลรวมจะเปลี่ยนไปอย่างต่อเนื่องในทุกมาตรฐาน มีการเปลี่ยนแปลงที่สำคัญหลายอย่างบนขอบฟ้า
ใน 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
ที่คอนสตรัคเตอร์และตัวอย่างนี้กลายเป็นรูปแบบที่ดี
ปัญหาทั่วไปที่เกิดขึ้นคือต้องการใช้ตัวemplace()
สร้างสไตล์กับมวลรวม:
struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error
สิ่งนี้ไม่ทำงานเนื่องจากemplace
จะพยายามทำการเริ่มต้นอย่างมีประสิทธิภาพX(1, 2)
ซึ่งไม่ถูกต้อง วิธีแก้ปัญหาทั่วไปคือการเพิ่มตัวสร้างX
แต่ด้วยข้อเสนอนี้ (ปัจจุบันทำงานผ่านแกน) มวลรวมจะได้อย่างมีประสิทธิภาพได้สังเคราะห์ตัวสร้างซึ่งทำในสิ่งที่ถูกต้อง - และทำตัวเหมือนตัวสร้างแบบปกติ รหัสข้างต้นจะรวบรวมตามที่เป็นอยู่ใน C ++ 20
ใน 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 (โดยไม่จำเป็นต้องมีคู่มือการหักเงินที่ผู้ใช้จัดหา)