เราต้องใช้ตัวสร้างสำเนาเมื่อใด


87

ฉันรู้ว่าคอมไพเลอร์ C ++ สร้างตัวสร้างการคัดลอกสำหรับคลาส ในกรณีใดที่เราต้องเขียนตัวสร้างสำเนาที่ผู้ใช้กำหนดเอง คุณสามารถยกตัวอย่างได้หรือไม่?



1
หนึ่งในกรณีที่ต้องเขียนสำเนาของคุณเอง: เมื่อคุณต้องทำสำเนาลึก โปรดทราบว่าทันทีที่คุณสร้าง ctor จะไม่มีการสร้าง ctor เริ่มต้นสำหรับคุณ (เว้นแต่คุณจะใช้คำหลักเริ่มต้น)
roughvchawla

คำตอบ:


75

ตัวสร้างการคัดลอกที่สร้างโดยคอมไพลเลอร์ทำการคัดลอกสมาชิกที่ชาญฉลาด บางครั้งนั่นก็ไม่เพียงพอ ตัวอย่างเช่น:

class Class {
public:
    Class( const char* str );
    ~Class();
private:
    char* stored;
};

Class::Class( const char* str )
{
    stored = new char[srtlen( str ) + 1 ];
    strcpy( stored, str );
}

Class::~Class()
{
    delete[] stored;
}

ในกรณีนี้การคัดลอกสมาชิกอย่างชาญฉลาดของstoredสมาชิกจะไม่ทำสำเนาบัฟเฟอร์ (เฉพาะตัวชี้เท่านั้นที่จะถูกคัดลอก) ดังนั้นสำเนาแรกที่ถูกทำลายการแชร์บัฟเฟอร์จะเรียกได้delete[]สำเร็จและตัวที่สองจะทำงานในลักษณะที่ไม่ได้กำหนด คุณต้องมีตัวสร้างการคัดลอกการคัดลอกแบบลึก (และตัวดำเนินการกำหนดด้วย)

Class::Class( const Class& another )
{
    stored = new char[strlen(another.stored) + 1];
    strcpy( stored, another.stored );
}

void Class::operator = ( const Class& another )
{
    char* temp = new char[strlen(another.stored) + 1];
    strcpy( temp, another.stored);
    delete[] stored;
    stored = temp;
}

10
มันไม่ได้ทำการคัดลอกแบบ bit-wise แต่เป็นสมาชิกที่ชาญฉลาดซึ่งโดยเฉพาะจะเรียกใช้ copy-ctor สำหรับสมาชิกประเภทคลาส
Georg Fritzsche

7
อย่าเขียนตัวดำเนินการมอบหมายแบบนั้น ไม่ยกเว้นปลอดภัย (ถ้าใหม่โยนข้อยกเว้นวัตถุจะถูกปล่อยให้อยู่ในสถานะที่ไม่ได้กำหนดโดยมีการจัดเก็บที่ชี้ไปที่ส่วนของหน่วยความจำที่ไม่ได้จัดสรร (ยกเลิกการจัดสรรหน่วยความจำหลังจากการดำเนินการทั้งหมดที่สามารถโยนได้สำเร็จแล้ว)) วิธีง่ายๆคือใช้ idium swap copy
Martin York

@sharptooth บรรทัดที่ 3 จากด้านล่างที่คุณมีdelete stored[];และฉันเชื่อว่ามันควรจะเป็นdelete [] stored;
Peter Ajtai

4
ฉันรู้ว่ามันเป็นเพียงตัวอย่าง std::stringแต่คุณควรจะชี้ทางออกที่ดีคือการใช้งาน แนวคิดทั่วไปคือเฉพาะคลาสยูทิลิตี้ที่จัดการทรัพยากรเท่านั้นที่ต้องใช้งาน Big Three มากเกินไปและคลาสอื่น ๆ ทั้งหมดควรใช้คลาสยูทิลิตี้เหล่านั้นโดยไม่จำเป็นต้องกำหนด Big Three ใด ๆ
GManNickG

2
@ มาร์ติน: ฉันต้องการให้แน่ใจว่ามันถูกแกะสลักด้วยหิน : P
GManNickG

46

ฉันรู้สึกแย่ที่กฎของการRule of Fiveไม่ถูกอ้างถึง

กฎนี้ง่ายมาก:

กฎข้อที่ห้า :
เมื่อใดก็ตามที่คุณเขียน Destructor, Copy Constructor, Copy Assignment Operator, Move Constructor หรือ Move Assignment Operator คุณอาจต้องเขียนอีกสี่ตัว

แต่มีแนวทางทั่วไปที่คุณควรปฏิบัติตามซึ่งเกิดจากความจำเป็นในการเขียนโค้ดที่มีข้อยกเว้น:

ทรัพยากรแต่ละรายการควรได้รับการจัดการโดยวัตถุเฉพาะ

ที่นี่@sharptooth's ยังคงเป็นรหัส (ส่วนใหญ่) ที่ดี แต่ถ้าเขาจะเพิ่มแอตทริบิวต์ที่สองให้กับชั้นเรียนของเขามันจะไม่เป็น พิจารณาคลาสต่อไปนี้:

class Erroneous
{
public:
  Erroneous();
  // ... others
private:
  Foo* mFoo;
  Bar* mBar;
};

Erroneous::Erroneous(): mFoo(new Foo()), mBar(new Bar()) {}

จะเกิดอะไรขึ้นถ้าnew Barพ่น? คุณจะลบวัตถุที่ชี้ไปได้mFooอย่างไร? มีวิธีแก้ปัญหา (ระดับฟังก์ชั่นลอง / จับ ... ) พวกเขาไม่ได้ปรับขนาด

วิธีที่เหมาะสมในการจัดการกับสถานการณ์คือใช้คลาสที่เหมาะสมแทนตัวชี้ดิบ

class Righteous
{
public:
private:
  std::unique_ptr<Foo> mFoo;
  std::unique_ptr<Bar> mBar;
};

ด้วยการใช้งานตัวสร้างเดียวกัน (หรือใช้จริงmake_unique) ตอนนี้ฉันมีข้อยกเว้นด้านความปลอดภัยฟรี !!! มันไม่น่าตื่นเต้น? และเหนือสิ่งอื่นใดฉันไม่ต้องกังวลเกี่ยวกับผู้ทำลายที่เหมาะสมอีกต่อไป! ฉันจำเป็นต้องเขียนของตัวเองCopy ConstructorและAssignment Operatorแม้ว่าunique_ptrไม่ได้กำหนดการดำเนินการเหล่านี้ ... แต่มันไม่สำคัญที่นี่;)

ดังนั้นsharptoothชั้นเรียนจึงกลับมาอีกครั้ง:

class Class
{
public:
  Class(char const* str): mData(str) {}
private:
  std::string mData;
};

ฉันไม่รู้เกี่ยวกับคุณ แต่ฉันคิดว่าของฉันง่ายกว่า;)


สำหรับ C ++ 11 - กฎห้าข้อซึ่งเพิ่มในกฎสามข้อของ Move Constructer และ Move Assignment Operator
Robert Andrzejuk

1
@Robb หมายเหตุ: ที่จริงที่แสดงให้เห็นในตัวอย่างสุดท้ายที่คุณควรโดยทั่วไปจุดมุ่งหมายสำหรับการปกครองของศูนย์ เฉพาะคลาสเทคนิคเฉพาะ (ทั่วไป) เท่านั้นที่ควรดูแลเกี่ยวกับการจัดการทรัพยากรหนึ่งคลาสอื่น ๆ ทั้งหมดควรใช้ตัวชี้ / คอนเทนเนอร์อัจฉริยะเหล่านั้นและไม่ต้องกังวลกับมัน
Matthieu M.

@MatthieuM. เห็นด้วย :-) ฉันพูดถึง Rule of Five เนื่องจากคำตอบนี้อยู่ก่อน C ++ 11 และขึ้นต้นด้วย "Big Three" แต่ควรกล่าวถึงว่าตอนนี้ "Big Five" มีความเกี่ยวข้องแล้ว ฉันไม่ต้องการโหวตคำตอบนี้เนื่องจากถูกต้องในบริบทที่ถาม
Robert Andrzejuk

@ Robb: ประเด็นดีฉันอัปเดตคำตอบเพื่อพูดถึง Rule of Five แทนที่จะเป็น Big Three หวังว่าคนส่วนใหญ่จะเปลี่ยนไปใช้คอมไพเลอร์ที่มีความสามารถ C ++ 11 ในตอนนี้ (และฉันสงสารคนที่ยังไม่มี)
Matthieu M.

31

ฉันจำได้จากการฝึกฝนของฉันและนึกถึงกรณีต่อไปนี้เมื่อต้องจัดการกับการประกาศ / กำหนดตัวสร้างสำเนาอย่างชัดเจน ฉันได้แบ่งกรณีออกเป็นสองประเภท

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


ความถูกต้อง / ความหมาย

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

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

วิธีทำให้คลาสไม่สามารถคัดลอกได้ใน C ++ 03

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

วิธีทำให้คลาสไม่สามารถคัดลอกได้ใน C ++ 11 หรือใหม่กว่า

ประกาศตัวสร้างการคัดลอกด้วย=deleteตอนท้าย


ตื้นและสำเนาลึก

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

  • การคัดลอกไฟล์ชั่วคราวบนดิสก์
  • การเปิดการเชื่อมต่อเครือข่ายแยกต่างหาก
  • การสร้างเธรดผู้ปฏิบัติงานแยกต่างหาก
  • การจัดสรรเฟรมบัฟเฟอร์ OpenGL แยกต่างหาก
  • ฯลฯ

การลงทะเบียนวัตถุด้วยตนเอง

พิจารณาคลาสที่อ็อบเจ็กต์ทั้งหมดไม่ว่าจะถูกสร้างขึ้นด้วยวิธีใด - ต้องลงทะเบียนอย่างใด ตัวอย่างบางส่วน:

  • ตัวอย่างที่ง่ายที่สุด: การรักษาจำนวนรวมของวัตถุที่มีอยู่ในปัจจุบัน การลงทะเบียนวัตถุเป็นเพียงการเพิ่มตัวนับแบบคงที่

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

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

การดำเนินการลงทะเบียนด้วยตนเองดังกล่าวจะต้องดำเนินการโดยตัวสร้างประเภทใด ๆ และตัวสร้างการคัดลอกก็ไม่มีข้อยกเว้น


วัตถุที่มีการอ้างอิงโยงภายใน

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

ตัวอย่าง:

struct MarriedMan;
struct MarriedWoman;

struct MarriedMan {
    // ...
    MarriedWoman* wife;   // association
};

struct MarriedWoman {
    // ...
    MarriedMan* husband;  // association
};

struct MarriedCouple {
    MarriedWoman wife;    // aggregation
    MarriedMan   husband; // aggregation

    MarriedCouple() {
        wife.husband = &husband;
        husband.wife = &wife;
    }
};

MarriedCouple couple1; // couple1.wife and couple1.husband are spouses

MarriedCouple couple2(couple1);
// Are couple2.wife and couple2.husband indeed spouses?
// Why does couple2.wife say that she is married to couple1.husband?
// Why does couple2.husband say that he is married to couple1.wife?

อนุญาตให้คัดลอกเฉพาะออบเจ็กต์ที่ตรงตามเกณฑ์ที่กำหนดเท่านั้น

อาจมีคลาสที่อ็อบเจ็กต์สามารถคัดลอกได้อย่างปลอดภัยในบางสถานะ (เช่น default-built-state) และไม่ปลอดภัยที่จะคัดลอกเป็นอย่างอื่น หากเราต้องการอนุญาตให้คัดลอกอ็อบเจ็กต์ที่ปลอดภัยต่อการคัดลอกดังนั้นหากตั้งโปรแกรมป้องกันเราจำเป็นต้องตรวจสอบรันไทม์ในตัวสร้างสำเนาที่ผู้ใช้กำหนดเอง


วัตถุย่อยที่ไม่สามารถคัดลอกได้

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


วัตถุย่อยที่สามารถคัดลอกได้

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

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

หรือต้องรองรับการคัดลอกทั้งแบบตื้นและแบบลึก (ไม่มีการรองรับการคัดลอกแบบตื้นและแบบลึก)

จากนั้นการตัดสินใจขั้นสุดท้ายจะถูกปล่อยให้กับผู้ใช้ประเภทนั้น - เมื่อคัดลอกวัตถุพวกเขาจะต้องระบุวิธีการคัดลอกที่ตั้งใจไว้อย่างชัดเจน (ผ่านอาร์กิวเมนต์เพิ่มเติม)

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


อย่าคัดลอกสถานะที่เกี่ยวข้องอย่างมากกับข้อมูลประจำตัวของวัตถุ

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

ตัวอย่าง:

  • UID ของออบเจ็กต์ (แต่อันนี้เป็นของกรณี "การลงทะเบียนด้วยตนเอง" จากด้านบนด้วยเนื่องจากต้องได้รับรหัสจากการลงทะเบียนด้วยตนเอง)

  • ประวัติของอ็อบเจ็กต์ (เช่น Undo / Redo stack) ในกรณีที่อ็อบเจ็กต์ใหม่ต้องไม่สืบทอดประวัติของอ็อบเจ็กต์ต้นทาง แต่ให้เริ่มต้นด้วยไอเท็มประวัติเดียว " คัดลอกเมื่อ <TIME> จาก <OTHER_OBJECT_ID> "

ในกรณีเช่นนี้ตัวสร้างการคัดลอกจะต้องข้ามการคัดลอกวัตถุย่อยที่เกี่ยวข้อง


การบังคับใช้ลายเซ็นที่ถูกต้องของตัวสร้างสำเนา

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

ตอนนี้จะเกิดอะไรขึ้นถ้าตัวสร้างการคัดลอก "การกลายพันธุ์" ของประเภทของวัตถุย่อยไม่ได้กลายพันธุ์ออบเจ็กต์ต้นทาง (และเขียนโดยโปรแกรมเมอร์ที่ไม่รู้เกี่ยวกับconstคีย์เวิร์ด)? หากเราไม่สามารถแก้ไขรหัสนั้นได้โดยการเพิ่มสิ่งที่ขาดหายไปconstตัวเลือกอื่นคือการประกาศตัวสร้างสำเนาที่ผู้ใช้กำหนดเองด้วยลายเซ็นที่ถูกต้องและกระทำบาปในการเปลี่ยนเป็นconst_castไฟล์.


คัดลอกเมื่อเขียน (COW)

คอนเทนเนอร์ COW ที่ให้การอ้างอิงโดยตรงไปยังข้อมูลภายในต้องคัดลอกในระดับลึกในขณะก่อสร้างมิฉะนั้นอาจทำงานเป็นหมายเลขอ้างอิงการนับอ้างอิง

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



การเพิ่มประสิทธิภาพ

ในกรณีต่อไปนี้คุณอาจต้องการ / จำเป็นต้องกำหนดตัวสร้างสำเนาของคุณเองโดยไม่คำนึงถึงการเพิ่มประสิทธิภาพ:


การเพิ่มประสิทธิภาพโครงสร้างระหว่างการคัดลอก

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


ข้ามการคัดลอกสถานะที่ไม่สามารถสังเกตได้

วัตถุอาจมีข้อมูลที่ไม่ได้เป็นส่วนหนึ่งของสถานะที่สังเกตได้ โดยปกติแล้วข้อมูลนี้จะถูกเก็บไว้ในแคช / บันทึกข้อมูลตลอดอายุการใช้งานของวัตถุเพื่อเร่งความเร็วในการดำเนินการสืบค้นที่ช้าบางอย่างที่ดำเนินการโดยวัตถุ สามารถข้ามการคัดลอกข้อมูลนั้นได้อย่างปลอดภัยเนื่องจากจะมีการคำนวณใหม่เมื่อ (และถ้า!) มีการดำเนินการที่เกี่ยวข้อง การคัดลอกข้อมูลนี้อาจไม่เป็นธรรมเนื่องจากอาจถูกยกเลิกได้อย่างรวดเร็วหากสถานะที่สังเกตได้ของวัตถุ (ซึ่งได้รับข้อมูลแคชมา) ถูกแก้ไขโดยการดำเนินการกลายพันธุ์ (และถ้าเราจะไม่แก้ไขวัตถุทำไมเราถึงสร้าง deep ก็อปปี้?)

การเพิ่มประสิทธิภาพนี้จะเป็นธรรมเฉพาะในกรณีที่ข้อมูลเสริมมีขนาดใหญ่เมื่อเทียบกับข้อมูลที่แสดงสถานะที่สังเกตได้


ปิดใช้งานการคัดลอกโดยนัย

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

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

C ++ 11 และมาตรฐานที่ใหม่กว่าอนุญาตให้ประกาศฟังก์ชันสมาชิกพิเศษ (ตัวสร้างค่าเริ่มต้นและตัวสร้างการคัดลอกตัวดำเนินการกำหนดสำเนาและตัวทำลาย) พร้อมกับการร้องขออย่างชัดเจนเพื่อใช้การใช้งานเริ่มต้น (เพียงแค่จบการประกาศด้วย=default)



สิ่งที่ต้องทำ

คำตอบนี้สามารถปรับปรุงได้ดังนี้:

  • เพิ่มโค้ดตัวอย่างเพิ่มเติม
  • ยกตัวอย่างกรณี "Objects with internal cross-references"
  • เพิ่มลิงค์

6

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

คุณจะต้องเขียนตัวสร้างสำเนาที่ไม่แล้วtitle = new char[length+1] strcpy(title, titleIn)ตัวสร้างสำเนาจะทำสำเนา "ตื้น"


2

Copy Constructor ถูกเรียกเมื่ออ็อบเจ็กต์ถูกส่งโดยค่าส่งคืนด้วยค่าหรือคัดลอกอย่างชัดเจน หากไม่มีตัวสร้างการคัดลอก c ++ จะสร้างตัวสร้างสำเนาเริ่มต้นซึ่งจะสร้างสำเนาแบบตื้น หากวัตถุไม่มีตัวชี้ไปยังหน่วยความจำที่จัดสรรแบบไดนามิกสำเนาตื้นจะทำ


0

มักเป็นความคิดที่ดีที่จะปิดการใช้งาน copy ctor และ operator = เว้นแต่ว่าคลาสจะต้องการโดยเฉพาะ สิ่งนี้อาจป้องกันความไร้ประสิทธิภาพเช่นการส่ง arg by value เมื่อมีการอ้างอิง นอกจากนี้วิธีการสร้างคอมไพลเลอร์อาจไม่ถูกต้อง


-1

ลองพิจารณาข้อมูลโค้ดด้านล่าง:

class base{
    int a, *p;
public:
    base(){
        p = new int;
    }
    void SetData(int, int);
    void ShowData();
    base(const base& old_ref){
        //No coding present.
    }
};
void base :: ShowData(){
    cout<<this->a<<" "<<*(this->p)<<endl;
}
void base :: SetData(int a, int b){
    this->a = a;
    *(this->p) = b;
}
int main(void)
{
    base b1;
    b1.SetData(2, 3);
    b1.ShowData();
    base b2 = b1; //!! Copy constructor called.
    b2.ShowData();
    return 0;
}

Output: 
2 3 //b1.ShowData();
1996774332 1205913761 //b2.ShowData();

b2.ShowData();ให้เอาต์พุตขยะเนื่องจากมีตัวสร้างการคัดลอกที่กำหนดโดยผู้ใช้ที่สร้างขึ้นโดยไม่มีโค้ดที่เขียนขึ้นเพื่อคัดลอกข้อมูลอย่างชัดเจน ดังนั้นคอมไพเลอร์ไม่สร้างเหมือนกัน

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

ไชโย ... โครตมีความสุข !!!

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