- อะไรคัดลอกวัตถุหมายถึง?
- ตัวสร้างสำเนาและตัวดำเนินการกำหนดค่าการคัดลอกคืออะไร
- ฉันต้องประกาศตัวเองเมื่อใด
- ฉันจะป้องกันไม่ให้คัดลอกวัตถุของฉันได้อย่างไร
คำตอบ:
C ++ ตัวแปรถือว่าผู้ใช้กำหนดประเภทที่มีความหมายค่า ซึ่งหมายความว่าวัตถุจะถูกคัดลอกโดยปริยายในบริบทต่าง ๆ และเราควรเข้าใจว่า "การคัดลอกวัตถุ" หมายถึงอะไรจริง ๆ
ขอให้เราพิจารณาตัวอย่างง่ายๆ
class person
{
std::string name;
int age;
public:
person(const std::string& name, int age) : name(name), age(age)
{
}
};
int main()
{
person a("Bjarne Stroustrup", 60);
person b(a); // What happens here?
b = a; // And here?
}
(หากคุณสับสนโดยname(name), age(age)
ส่วนนี้เรียกว่ารายการสมาชิกเริ่มต้น )
การคัดลอกperson
วัตถุหมายถึงอะไร main
ฟังก์ชั่นแสดงให้เห็นสองสถานการณ์ที่แตกต่างกันการคัดลอก การเริ่มต้นperson b(a);
ที่จะดำเนินการโดยตัวสร้างสำเนา หน้าที่ของมันคือการสร้างวัตถุใหม่ขึ้นอยู่กับสถานะของวัตถุที่มีอยู่ ได้รับมอบหมายb = a
จะดำเนินการโดยผู้ประกอบการที่ได้รับมอบหมายสำเนา โดยทั่วไปแล้วงานของมันจะซับซ้อนกว่านี้เล็กน้อยเนื่องจากวัตถุเป้าหมายอยู่ในสถานะที่ถูกต้องบางอย่างที่ต้องได้รับการจัดการ
เนื่องจากเราไม่ได้ประกาศตัวสร้างสำเนาหรือตัวดำเนินการกำหนด (หรือตัวทำลาย) เองเราจึงกำหนดสิ่งเหล่านี้โดยปริยาย อ้างอิงจากมาตรฐาน:
ตัวสร้างการคัดลอก [... ] และตัวดำเนินการกำหนดค่าการคัดลอก [... ] และ destructor เป็นฟังก์ชันสมาชิกพิเศษ [ หมายเหตุ : การใช้งานจะประกาศฟังก์ชั่นสมาชิกเหล่านี้โดยนัยสำหรับคลาสบางประเภทเมื่อโปรแกรมไม่ได้ประกาศอย่างชัดเจน การใช้งานจะกำหนดโดยนัยหากมีการใช้งาน [... ] end note ] [n3126.pdf ตอนที่ 12 §1]
โดยค่าเริ่มต้นการคัดลอกวัตถุหมายถึงการคัดลอกสมาชิก:
ตัวสร้างสำเนาที่กำหนดโดยนัยสำหรับ non-union class X ทำการคัดลอกรหัสสมาชิกของ subobjects [n3126.pdf ส่วนที่ 12.8 §16]
ตัวดำเนินการกำหนดค่าการคัดลอกที่กำหนดโดยนัยสำหรับ non-union class X ดำเนินการกำหนดสำเนาของสมาชิกย่อยของ subobjects [n3126.pdf ส่วนที่ 12.8 §30]
ฟังก์ชั่นสมาชิกพิเศษที่กำหนดโดยนัยสำหรับperson
ลักษณะดังนี้:
// 1. copy constructor
person(const person& that) : name(that.name), age(that.age)
{
}
// 2. copy assignment operator
person& operator=(const person& that)
{
name = that.name;
age = that.age;
return *this;
}
// 3. destructor
~person()
{
}
การคัดลอกแบบสมาชิกเป็นสิ่งที่เราต้องการในกรณีนี้
name
และage
คัดลอกดังนั้นเราจึงได้person
วัตถุที่มีอยู่ในตัวและเป็นอิสระ destructor ที่กำหนดโดยปริยายนั้นว่างเปล่าเสมอ ในกรณีนี้ก็ดีเช่นกันเนื่องจากเราไม่ได้รับทรัพยากรใด ๆ ในตัวสร้าง destructors ของสมาชิกจะถูกเรียกโดยปริยายหลังจากตัวperson
ทำลายเสร็จสิ้น:
หลังจากดำเนินการร่างของ destructor และทำลายวัตถุอัตโนมัติใด ๆ ที่จัดสรรภายในร่างกาย destructor สำหรับคลาส X เรียก destructors สำหรับสมาชิก X โดยตรง [... ] [n3126.pdf 12.4 §6]
ดังนั้นเมื่อไหร่เราจึงควรประกาศฟังก์ชั่นสมาชิกพิเศษเหล่านั้นอย่างชัดเจน? เมื่อคลาสของเราจัดการทรัพยากรนั่นคือเมื่อวัตถุของคลาสรับผิดชอบทรัพยากรนั้น ซึ่งมักจะหมายถึงทรัพยากรที่ได้มาในตัวสร้าง (หรือส่งผ่านไปยังตัวสร้าง) และปล่อยในตัวทำลาย
ให้เราย้อนเวลากลับไปสู่มาตรฐาน C ++ ล่วงหน้า ไม่มีสิ่งเช่นstd::string
นั้นและโปรแกรมเมอร์ก็หลงรักพอยน์เตอร์ person
ชั้นอาจจะมีการมองเช่นนี้
class person
{
char* name;
int age;
public:
// the constructor acquires a resource:
// in this case, dynamic memory obtained via new[]
person(const char* the_name, int the_age)
{
name = new char[strlen(the_name) + 1];
strcpy(name, the_name);
age = the_age;
}
// the destructor must release this resource via delete[]
~person()
{
delete[] name;
}
};
แม้กระทั่งทุกวันนี้ผู้คนยังคงเขียนคลาสในลักษณะนี้และมีปัญหา: " ฉันผลักคนไปสู่เวกเตอร์และตอนนี้ฉันได้รับข้อผิดพลาดที่หน่วยความจำบ้า! " โปรดจำไว้ว่าโดยค่าเริ่มต้นการคัดลอกวัตถุหมายถึงการคัดลอกname
สมาชิก คัดลอกตัวชี้ไม่ใช่อาร์เรย์อักขระที่ชี้ไป! สิ่งนี้มีผลกระทบที่ไม่พึงประสงค์หลายประการ:
a
b
b
ถูกทำลายa.name
จะเป็นตัวชี้ห้อยa
ถูกทำลายลบผลผลิตตัวชี้ห้อยไม่ได้กำหนดพฤติกรรมname
ชี้ไปก่อนการมอบหมายไม่ช้าก็เร็วคุณจะได้รับการรั่วไหลของหน่วยความจำทั่วทุกสถานที่เนื่องจากการคัดลอกแบบสมาชิกแบบไม่มีผลตามที่ต้องการเราต้องกำหนดตัวสร้างการคัดลอกและตัวดำเนินการกำหนดค่าการคัดลอกอย่างชัดเจนเพื่อทำสำเนาลึกของอาร์เรย์อักขระ:
// 1. copy constructor
person(const person& that)
{
name = new char[strlen(that.name) + 1];
strcpy(name, that.name);
age = that.age;
}
// 2. copy assignment operator
person& operator=(const person& that)
{
if (this != &that)
{
delete[] name;
// This is a dangerous point in the flow of execution!
// We have temporarily invalidated the class invariants,
// and the next statement might throw an exception,
// leaving the object in an invalid state :(
name = new char[strlen(that.name) + 1];
strcpy(name, that.name);
age = that.age;
}
return *this;
}
จดบันทึกความแตกต่างระหว่างการเริ่มต้นและการกำหนด: เราต้องทำลายสถานะเก่าก่อนกำหนดname
เพื่อป้องกันการรั่วไหลของหน่วยความจำ นอกจากนี้เรายังต้องป้องกันการมอบหมายรูปแบบx = x
ตนเอง หากไม่มีการตรวจสอบนั้นdelete[] name
จะลบอาร์เรย์ที่มีสตริงต้นฉบับเพราะเมื่อคุณเขียนx = x
ทั้งสองthis->name
และthat.name
มีตัวชี้เดียวกัน
น่าเสียดายที่โซลูชันนี้จะล้มเหลวหากnew char[...]
ส่งข้อยกเว้นเนื่องจากหน่วยความจำหมด ทางออกหนึ่งที่เป็นไปได้คือการแนะนำตัวแปรท้องถิ่นและจัดลำดับคำสั่งใหม่:
// 2. copy assignment operator
person& operator=(const person& that)
{
char* local_name = new char[strlen(that.name) + 1];
// If the above statement throws,
// the object is still in the same state as before.
// None of the following statements will throw an exception :)
strcpy(local_name, that.name);
delete[] name;
name = local_name;
age = that.age;
return *this;
}
นอกจากนี้ยังดูแลการมอบหมายด้วยตนเองโดยไม่ตรวจสอบอย่างชัดเจน ทางออกที่แข็งแกร่งยิ่งขึ้นสำหรับปัญหานี้คือสำนวนคัดลอกและสลับแต่ฉันจะไม่เข้าไปดูรายละเอียดของข้อยกเว้นด้านความปลอดภัยที่นี่ ฉันพูดถึงข้อยกเว้นเท่านั้นเพื่อให้ประเด็นต่อไปนี้: การเขียนคลาสที่จัดการทรัพยากรทำได้ยาก
ทรัพยากรบางอย่างไม่สามารถหรือไม่ควรคัดลอกเช่นตัวจัดการไฟล์หรือ mutex ในกรณีนี้ให้ประกาศตัวสร้างการคัดลอกและตัวดำเนินการกำหนดค่าการคัดลอกprivate
โดยไม่ต้องให้คำจำกัดความ:
private:
person(const person& that);
person& operator=(const person& that);
หรือคุณสามารถรับช่วงต่อจากboost::noncopyable
หรือประกาศเป็นลบ (ใน C ++ 11 ขึ้นไป):
person(const person& that) = delete;
person& operator=(const person& that) = delete;
บางครั้งคุณต้องใช้คลาสที่จัดการทรัพยากร (ไม่เคยจัดการทรัพยากรหลายอย่างในคลาสเดียวสิ่งนี้จะนำไปสู่ความเจ็บปวดเท่านั้น) ในกรณีนี้ให้จดจำกฎข้อที่สาม :
หากคุณต้องการประกาศตัว Destructor ตัวคัดลอก Constructor หรือตัวดำเนินการกำหนดค่าด้วยตนเองอย่างชัดเจนคุณอาจต้องประกาศทั้งสามอย่างชัดเจน
(น่าเสียดายที่ "กฎ" นี้ไม่ได้บังคับใช้โดยมาตรฐาน C ++ หรือคอมไพเลอร์ใด ๆ ที่ฉันรู้จัก)
ตั้งแต่ C ++ 11 เป็นต้นไปวัตถุจะมีฟังก์ชันสมาชิกพิเศษเพิ่มอีก 2 ฟังก์ชันคือตัวสร้างการย้ายและการกำหนดย้าย กฎของห้ารัฐเพื่อใช้ฟังก์ชันเหล่านี้เช่นกัน
ตัวอย่างที่มีลายเซ็นต์:
class person
{
std::string name;
int age;
public:
person(const std::string& name, int age); // Ctor
person(const person &) = default; // Copy Ctor
person(person &&) noexcept = default; // Move Ctor
person& operator=(const person &) = default; // Copy Assignment
person& operator=(person &&) noexcept = default; // Move Assignment
~person() noexcept = default; // Dtor
};
กฎ 3/5 ยังถูกอ้างถึงเป็นกฎของ 3/3/5 ส่วนที่เป็นศูนย์ของกฎระบุว่าคุณได้รับอนุญาตให้ไม่เขียนฟังก์ชันสมาชิกพิเศษใด ๆ เมื่อสร้างคลาสของคุณ
ส่วนใหญ่คุณไม่จำเป็นต้องจัดการทรัพยากรด้วยตัวเองเพราะคลาสที่มีอยู่แล้วเช่นนั้นstd::string
มีไว้สำหรับคุณแล้ว เพียงเปรียบเทียบรหัสอย่างง่ายโดยใช้std::string
สมาชิกกับทางเลือกที่ซับซ้อนและผิดพลาดโดยใช้ a char*
และคุณควรมั่นใจ ตราบใดที่คุณอยู่ห่างจากสมาชิกตัวชี้แบบดิบกฎสามข้อนั้นไม่น่าจะเกี่ยวกับรหัสของคุณเอง
กฎสามเป็นกฎของหัวแม่มือสำหรับ C ++ โดยทั่วไปกล่าวว่า
หากชั้นเรียนของคุณต้องการ
- นวกรรมิกสำเนา ,
- ผู้ประกอบการที่ได้รับมอบหมาย ,
- หรือdestructor ,
กำหนด explictly แล้วมันเป็นแนวโน้มที่จะต้องทั้งสามของพวกเขา
เหตุผลนี้เป็นเพราะทั้งสามคนมักจะใช้ในการจัดการทรัพยากรและถ้าคลาสของคุณจัดการทรัพยากรก็มักจะต้องจัดการการคัดลอกเช่นเดียวกับการปลดปล่อย
ถ้าไม่มีความหมายที่ดีสำหรับการคัดลอกทรัพยากรชั้นของคุณจัดการแล้วพิจารณาที่จะห้ามการคัดลอกด้วยการประกาศ (ไม่กำหนด ) private
ตัวสร้างสำเนาและผู้ประกอบการที่ได้รับมอบหมายเป็น
(โปรดทราบว่าเวอร์ชันใหม่ที่กำลังจะมาถึงของมาตรฐาน C ++ (ซึ่งคือ C ++ 11) เพิ่มซีแมนทิกส์ย้ายเป็น C ++ ซึ่งจะเปลี่ยนกฎของสามอย่างไรก็ตามฉันรู้น้อยเกินไปเกี่ยวกับเรื่องนี้ในการเขียนส่วน C ++ 11 เกี่ยวกับกฎข้อที่สาม)
boost::noncopyable
) นอกจากนี้ยังสามารถชัดเจนมาก ฉันคิดว่า C ++ 0x และความเป็นไปได้ในการ "ลบ" ฟังก์ชั่นสามารถช่วยได้ที่นี่ แต่ลืมไวยากรณ์: /
noncopyable
เป็นส่วนหนึ่งของ std lib ฉันไม่คิดว่ามันจะเป็นการปรับปรุงมากนัก (Oh, และหากคุณลืมไวยากรณ์การลบให้คุณลืมอีธานหมอที่ผมเคยรู้. :)
)
กฎของทั้งสามนั้นเป็นไปตามที่ระบุไว้ข้างต้น
ตัวอย่างง่ายๆในภาษาอังกฤษธรรมดา ๆ ของปัญหาที่แก้ได้:
ตัวทำลายที่ไม่ใช่ค่าเริ่มต้น
คุณจัดสรรหน่วยความจำในตัวสร้างของคุณและคุณต้องเขียน destructor เพื่อลบ มิฉะนั้นคุณจะทำให้หน่วยความจำรั่ว
คุณอาจคิดว่านี่เป็นงานที่ทำเสร็จแล้ว
ปัญหาจะเกิดขึ้นถ้าสำเนาทำจากวัตถุของคุณสำเนาก็จะชี้ไปยังหน่วยความจำเดียวกันกับวัตถุต้นฉบับ
เมื่อหนึ่งในสิ่งเหล่านี้ลบหน่วยความจำใน destructor ของอื่น ๆ จะมีตัวชี้ไปยังหน่วยความจำที่ไม่ถูกต้อง (นี้เรียกว่าตัวชี้ห้อย) เมื่อพยายามที่จะใช้มันสิ่งที่จะได้รับขน
ดังนั้นคุณเขียนตัวสร้างการคัดลอกเพื่อที่จะจัดสรรวัตถุใหม่ชิ้นส่วนหน่วยความจำของตัวเองที่จะทำลาย
ผู้ประกอบการกำหนดและคัดลอกคอนสตรัค
คุณจัดสรรหน่วยความจำในตัวสร้างของคุณไปยังตัวชี้สมาชิกของคลาสของคุณ เมื่อคุณคัดลอกวัตถุของคลาสนี้ตัวดำเนินการกำหนดค่าเริ่มต้นและตัวสร้างการคัดลอกจะคัดลอกค่าของตัวชี้สมาชิกนี้ไปยังวัตถุใหม่
ซึ่งหมายความว่าวัตถุใหม่และวัตถุเก่าจะชี้ไปที่หน่วยความจำชิ้นเดียวกันดังนั้นเมื่อคุณเปลี่ยนวัตถุในวัตถุหนึ่งวัตถุนั้นจะถูกเปลี่ยนสำหรับวัตถุอื่น ๆ ด้วยเช่นกัน หากวัตถุหนึ่งลบหน่วยความจำนี้วัตถุอื่นจะพยายามใช้มันต่อไป
ในการแก้ไขปัญหานี้คุณต้องเขียนตัวสร้างสำเนาและโอเปอเรเตอร์การมอบหมายเวอร์ชันของคุณเอง เวอร์ชันของคุณจัดสรรหน่วยความจำแยกไปยังวัตถุใหม่และคัดลอกค่าที่ตัวชี้แรกชี้ไปยังที่อยู่
โดยทั่วไปหากคุณมี destructor (ไม่ใช่ destructor เริ่มต้น) หมายความว่าคลาสที่คุณกำหนดมีการจัดสรรหน่วยความจำบางส่วน สมมติว่ามีการใช้คลาสภายนอกโดยรหัสลูกค้าหรือคุณ
MyClass x(a, b);
MyClass y(c, d);
x = y; // This is a shallow copy if assignment operator is not provided
ถ้า MyClass มีสมาชิกที่พิมพ์แบบดั้งเดิมเพียงตัวเดียวตัวดำเนินการกำหนดค่าเริ่มต้นจะทำงาน แต่ถ้ามีสมาชิกตัวชี้และวัตถุที่ไม่มีตัวดำเนินการกำหนดค่าผลลัพธ์จะไม่สามารถคาดเดาได้ ดังนั้นเราจึงสามารถพูดได้ว่าหากมีสิ่งที่จะลบใน destructor ของชั้นเรียนเราอาจต้องการตัวดำเนินการคัดลอกลึกซึ่งหมายความว่าเราควรให้ตัวสร้างสำเนาและตัวดำเนินการกำหนด
การคัดลอกวัตถุหมายถึงอะไร มีหลายวิธีที่คุณสามารถคัดลอกวัตถุได้ - ลองพูดถึง 2 ชนิดที่คุณน่าจะหมายถึง - สำเนาลึกและสำเนาตื้น ๆ
เนื่องจากเราใช้ภาษาเชิงวัตถุ (หรืออย่างน้อยก็สมมติ) ดังนั้นสมมุติว่าคุณมีหน่วยความจำที่จัดสรรไว้ เนื่องจากเป็นภาษา OO เราสามารถอ้างถึงกลุ่มของหน่วยความจำที่เราจัดสรรได้อย่างง่ายดายเพราะโดยปกติแล้วจะเป็นตัวแปรดั้งเดิม (ints, chars, bytes) หรือคลาสที่เรากำหนดไว้ซึ่งทำจากประเภทและดั้งเดิมของเราเอง สมมุติว่าเรามีคลาสของ Car ดังนี้:
class Car //A very simple class just to demonstrate what these definitions mean.
//It's pseudocode C++/Javaish, I assume strings do not need to be allocated.
{
private String sPrintColor;
private String sModel;
private String sMake;
public changePaint(String newColor)
{
this.sPrintColor = newColor;
}
public Car(String model, String make, String color) //Constructor
{
this.sPrintColor = color;
this.sModel = model;
this.sMake = make;
}
public ~Car() //Destructor
{
//Because we did not create any custom types, we aren't adding more code.
//Anytime your object goes out of scope / program collects garbage / etc. this guy gets called + all other related destructors.
//Since we did not use anything but strings, we have nothing additional to handle.
//The assumption is being made that the 3 strings will be handled by string's destructor and that it is being called automatically--if this were not the case you would need to do it here.
}
public Car(const Car &other) // Copy Constructor
{
this.sPrintColor = other.sPrintColor;
this.sModel = other.sModel;
this.sMake = other.sMake;
}
public Car &operator =(const Car &other) // Assignment Operator
{
if(this != &other)
{
this.sPrintColor = other.sPrintColor;
this.sModel = other.sModel;
this.sMake = other.sMake;
}
return *this;
}
}
สำเนาลึกคือถ้าเราประกาศวัตถุแล้วสร้างสำเนาของวัตถุที่แยกจากกันอย่างสมบูรณ์ ... เราจบด้วย 2 วัตถุใน 2 ชุดหน่วยความจำอย่างสมบูรณ์
Car car1 = new Car("mustang", "ford", "red");
Car car2 = car1; //Call the copy constructor
car2.changePaint("green");
//car2 is now green but car1 is still red.
ตอนนี้ขอทำสิ่งที่แปลก สมมติว่า car2 นั้นมีการตั้งโปรแกรมผิดหรือตั้งใจที่จะแบ่งปันหน่วยความจำจริงที่ car1 ทำขึ้นมา (โดยปกติจะเป็นความผิดพลาดในการทำเช่นนี้และในชั้นเรียนมักเป็นผ้าห่มที่กล่าวถึงข้างใต้) แกล้งทำเป็นว่าเมื่อใดก็ตามที่คุณถามเกี่ยวกับ car2 คุณจะแก้ไขตัวชี้ไปยังพื้นที่หน่วยความจำของ car1 จริง ๆ ... คือ.
//Shallow copy example
//Assume we're in C++ because it's standard behavior is to shallow copy objects if you do not have a constructor written for an operation.
//Now let's assume I do not have any code for the assignment or copy operations like I do above...with those now gone, C++ will use the default.
Car car1 = new Car("ford", "mustang", "red");
Car car2 = car1;
car2.changePaint("green");//car1 is also now green
delete car2;/*I get rid of my car which is also really your car...I told C++ to resolve
the address of where car2 exists and delete the memory...which is also
the memory associated with your car.*/
car1.changePaint("red");/*program will likely crash because this area is
no longer allocated to the program.*/
ดังนั้นไม่ว่าคุณจะเขียนด้วยภาษาใดให้ระวังเกี่ยวกับสิ่งที่คุณหมายถึงเมื่อมันมาถึงการคัดลอกวัตถุเพราะส่วนใหญ่เวลาที่คุณต้องการสำเนาลึก
ตัวสร้างสำเนาและตัวดำเนินการกำหนดค่าการคัดลอกคืออะไร ฉันใช้ไปแล้วข้างต้น ตัวสร้างการคัดลอกจะถูกเรียกใช้เมื่อคุณพิมพ์รหัสเช่นที่Car car2 = car1;
สำคัญถ้าคุณประกาศตัวแปรและกำหนดในบรรทัดเดียวนั่นคือเมื่อตัวสร้างการคัดลอกถูกเรียก ผู้ดำเนินการที่ได้รับมอบหมายคือสิ่งที่เกิดขึ้นเมื่อคุณใช้เครื่องหมายเท่ากับcar2 = car1;
- ประกาศcar2
ไม่ได้ประกาศในคำสั่งเดียวกัน โค้ดสองชิ้นที่คุณเขียนสำหรับการดำเนินการเหล่านี้มีแนวโน้มที่คล้ายกันมาก ในความเป็นจริงรูปแบบการออกแบบโดยทั่วไปมีฟังก์ชั่นอื่นที่คุณเรียกใช้เพื่อตั้งค่าทุกอย่างเมื่อคุณพอใจกับการคัดลอก / การมอบหมายเริ่มต้นที่ถูกต้อง - ถ้าคุณดูรหัสยาว ๆ ที่ฉันเขียน
ฉันต้องประกาศตัวเองเมื่อใด หากคุณไม่ได้เขียนรหัสที่จะแบ่งปันหรือเพื่อการผลิตในบางลักษณะคุณจะต้องแจ้งให้พวกเขาทราบเมื่อคุณต้องการเท่านั้น คุณต้องระวังว่าภาษาโปรแกรมของคุณทำอะไรถ้าคุณเลือกที่จะใช้ 'โดยไม่ตั้งใจ' และไม่ได้ทำเช่นคุณได้รับคอมไพเลอร์เริ่มต้น ฉันไม่ค่อยใช้ตัวสร้างสำเนาเช่น แต่การแทนที่โอเปอเรเตอร์การมอบหมายเป็นเรื่องธรรมดา คุณรู้หรือไม่ว่าคุณสามารถลบล้างการบวกการลบและอื่น ๆ ได้เช่นกัน
ฉันจะป้องกันไม่ให้คัดลอกวัตถุของฉันได้อย่างไร แทนที่วิธีทั้งหมดที่คุณได้รับอนุญาตให้จัดสรรหน่วยความจำสำหรับวัตถุของคุณด้วยฟังก์ชันส่วนตัวเป็นการเริ่มต้นที่สมเหตุสมผล หากคุณไม่ต้องการให้ผู้อื่นคัดลอกพวกเขาคุณสามารถทำให้เป็นสาธารณะและแจ้งเตือนโปรแกรมเมอร์โดยการโยนข้อยกเว้นและไม่คัดลอกวัตถุ
ฉันต้องประกาศตัวเองเมื่อใด
กฎของสามระบุว่าถ้าคุณประกาศใด ๆ
จากนั้นคุณควรประกาศทั้งสาม มันขยายตัวออกมาจากการสังเกตว่าความจำเป็นที่จะต้องใช้ความหมายของการดำเนินการคัดลอกมักจะเกิดจากชั้นเรียนที่มีการจัดการทรัพยากรบางชนิดและเกือบจะส่อให้เห็นว่า
การจัดการทรัพยากรใด ๆ ที่กำลังทำอยู่ในการคัดลอกหนึ่งครั้งอาจจำเป็นต้องทำในการคัดลอกอื่นและ
ตัวทำลายคลาสจะเข้าร่วมในการจัดการทรัพยากร (โดยปกติจะปล่อยออกมา) ทรัพยากรแบบคลาสสิกที่จะจัดการคือหน่วยความจำและนี่คือสาเหตุที่คลาสไลบรารีมาตรฐานทั้งหมดที่จัดการหน่วยความจำ (เช่นคอนเทนเนอร์ STL ที่ดำเนินการจัดการหน่วยความจำแบบไดนามิก) ประกาศทั้งหมดว่า "สามตัวใหญ่": ทั้งการคัดลอกและ destructor
ผลที่ตามมาของกฎข้อที่สามคือการมีผู้ทำลายประกาศโดยผู้ใช้บ่งชี้ว่าการคัดลอกสมาชิกที่เรียบง่ายนั้นไม่น่าจะเหมาะสมสำหรับการดำเนินการคัดลอกในชั้นเรียน ในทางกลับกันแสดงให้เห็นว่าถ้าชั้นเรียนประกาศตัวทำลายล้างการดำเนินการคัดลอกอาจไม่ควรสร้างขึ้นโดยอัตโนมัติเพราะพวกเขาจะไม่ทำสิ่งที่ถูกต้อง ในขณะที่มีการนำ C ++ 98 มาใช้ความสำคัญของการให้เหตุผลนี้ยังไม่ได้รับการยอมรับอย่างเต็มที่ดังนั้นใน C ++ 98 การมีอยู่ของผู้ใช้ที่ประกาศตัวทำลายระบบไม่มีผลกระทบต่อความตั้งใจของคอมไพเลอร์ในการสร้างการคัดลอก ซึ่งยังคงเป็นกรณีใน C ++ 11 แต่เพียงเพราะการ จำกัด เงื่อนไขที่การดำเนินการคัดลอกถูกสร้างขึ้นจะทำให้รหัสดั้งเดิมเสียหายมากเกินไป
ฉันจะป้องกันไม่ให้คัดลอกวัตถุของฉันได้อย่างไร
ประกาศตัวคัดลอกคอนสตรัค & ตัวดำเนินการกำหนดค่าให้เป็นตัวระบุการเข้าถึงส่วนตัว
class MemoryBlock
{
public:
//code here
private:
MemoryBlock(const MemoryBlock& other)
{
cout<<"copy constructor"<<endl;
}
// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other)
{
return *this;
}
};
int main()
{
MemoryBlock a;
MemoryBlock b(a);
}
ใน C ++ 11 เป็นต้นไปคุณสามารถประกาศตัวสร้างสำเนาและตัวดำเนินการกำหนดค่าที่ถูกลบ
class MemoryBlock
{
public:
MemoryBlock(const MemoryBlock& other) = delete
// Copy assignment operator.
MemoryBlock& operator=(const MemoryBlock& other) =delete
};
int main()
{
MemoryBlock a;
MemoryBlock b(a);
}
คำตอบที่มีอยู่จำนวนมากสัมผัสตัวสร้างสำเนาตัวดำเนินการกำหนดและ destructor แล้ว อย่างไรก็ตามในการโพสต์ C ++ 11 การแนะนำของ semantic ย้ายอาจขยายเกิน 3
เมื่อเร็ว ๆ นี้ Michael Claisse ได้พูดคุยที่แตะหัวข้อนี้: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class
กฎสามข้อใน C ++ เป็นหลักการพื้นฐานของการออกแบบและการพัฒนาข้อกำหนดสามข้อที่หากมีคำจำกัดความที่ชัดเจนในหนึ่งในฟังก์ชั่นสมาชิกต่อไปนี้โปรแกรมเมอร์ควรกำหนดฟังก์ชั่นสมาชิกอีกสองคนด้วยกัน ฟังก์ชันสมาชิกสามรายการต่อไปนี้จำเป็นอย่างยิ่ง: destructor, ตัวสร้างสำเนา, ตัวดำเนินการกำหนดค่าการคัดลอก
ตัวสร้างการคัดลอกใน C ++ เป็นตัวสร้างพิเศษ มันถูกใช้เพื่อสร้างวัตถุใหม่ซึ่งเป็นวัตถุใหม่ที่เทียบเท่ากับสำเนาของวัตถุที่มีอยู่
ตัวดำเนินการกำหนดค่าการคัดลอกเป็นตัวดำเนินการกำหนดพิเศษที่มักใช้เพื่อระบุวัตถุที่มีอยู่ให้ผู้อื่นของวัตถุชนิดเดียวกัน
มีตัวอย่างรวดเร็ว:
// default constructor
My_Class a;
// copy constructor
My_Class b(a);
// copy constructor
My_Class c = a;
// copy assignment operator
b = a;
c++-faq
ก่อนที่คุณจะลงคะแนนใกล้