ฉันรู้ว่ามันเป็นแนวปฏิบัติที่ดีในการประกาศ destructors เสมือนจริงสำหรับคลาสพื้นฐานใน C ++ แต่เป็นสิ่งสำคัญเสมอที่จะประกาศvirtual
destructors แม้สำหรับคลาสนามธรรมที่ทำหน้าที่เป็นอินเตอร์เฟสหรือไม่ โปรดระบุเหตุผลและตัวอย่างว่าทำไม
ฉันรู้ว่ามันเป็นแนวปฏิบัติที่ดีในการประกาศ destructors เสมือนจริงสำหรับคลาสพื้นฐานใน C ++ แต่เป็นสิ่งสำคัญเสมอที่จะประกาศvirtual
destructors แม้สำหรับคลาสนามธรรมที่ทำหน้าที่เป็นอินเตอร์เฟสหรือไม่ โปรดระบุเหตุผลและตัวอย่างว่าทำไม
คำตอบ:
มันสำคัญยิ่งกว่าสำหรับอินเตอร์เฟส ผู้ใช้ในชั้นเรียนของคุณอาจถือตัวชี้ไปยังส่วนต่อประสานไม่ใช่ตัวชี้ไปยังการใช้งานที่เป็นรูปธรรม เมื่อพวกเขามาลบมันถ้า destructor ไม่ใช่เสมือนพวกเขาจะเรียก destructor ของอินเทอร์เฟซ (หรือค่าเริ่มต้นที่คอมไพเลอร์ให้ถ้าคุณไม่ได้ระบุหนึ่ง) ไม่ใช่ destructor ของคลาสที่ได้รับ หน่วยความจำรั่วทันที
ตัวอย่างเช่น
class Interface
{
virtual void doSomething() = 0;
};
class Derived : public Interface
{
Derived();
~Derived()
{
// Do some important cleanup...
}
};
void myFunc(void)
{
Interface* p = new Derived();
// The behaviour of the next line is undefined. It probably
// calls Interface::~Interface, not Derived::~Derived
delete p;
}
[expr.delete]/
... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. ...
มันจะยังไม่ได้กำหนดถ้า Derived ใช้ destructor ที่สร้างขึ้นโดยปริยาย
คำตอบสำหรับคำถามของคุณมักจะ แต่ไม่เสมอไป ถ้าคลาสนามธรรมของคุณห้ามให้ลูกค้าโทรลบตัวชี้ (หรือถ้ามันบอกไว้ในเอกสารประกอบ) คุณมีอิสระที่จะไม่ประกาศตัวทำลายเสมือน
คุณสามารถห้ามลูกค้าโทรลบบนตัวชี้ไปที่มันโดยการป้องกัน destructor การทำงานเช่นนี้จะปลอดภัยอย่างสมบูรณ์และเหมาะสมในการละเว้นตัวทำลายเสมือน
ในที่สุดคุณจะจบลงด้วยไม่มีตารางวิธีเสมือนและท้ายที่สุดการส่งสัญญาณลูกค้าของคุณความตั้งใจในการทำให้มันไม่สามารถลบผ่านตัวชี้ไปดังนั้นคุณมีเหตุผลที่จะไม่ประกาศเสมือนจริงในกรณีเหล่านั้น
[ดูรายการ 4 ในบทความนี้: http://www.gotw.ca/publications/mill18.htm ]
ฉันตัดสินใจทำการวิจัยและพยายามสรุปคำตอบของคุณ คำถามต่อไปนี้จะช่วยคุณในการตัดสินใจว่าคุณต้องการ destructor ประเภทใด:
ฉันหวังว่านี่จะช่วยได้.
*เป็นเรื่องสำคัญที่จะต้องทราบว่าไม่มีวิธีใดใน C ++ ที่จะทำเครื่องหมายคลาสว่าสุดท้าย (เช่นไม่ใช่ subclassable) ดังนั้นในกรณีที่คุณตัดสินใจที่จะประกาศ destructor ของคุณที่ไม่ใช่เสมือนและสาธารณะอย่าลืมเตือนโปรแกรมเมอร์ของคุณให้ชัดเจน มาจากชั้นเรียนของคุณ
อ้างอิง:
ใช่มันเป็นสิ่งสำคัญเสมอ คลาสที่ได้รับอาจจัดสรรหน่วยความจำหรือเก็บการอ้างอิงไปยังทรัพยากรอื่น ๆ ที่จะต้องล้างข้อมูลเมื่อวัตถุถูกทำลาย หากคุณไม่ได้ให้อินเทอร์เฟซ / นามธรรมคลาสเสมือน destructors ดังนั้นทุกครั้งที่คุณลบอินสแตนซ์ของคลาสที่ได้รับผ่านคลาสพื้นฐานจัดการ destructor คลาสที่ได้รับของคุณจะไม่ถูกเรียก
ดังนั้นคุณกำลังเปิดโอกาสให้หน่วยความจำรั่ว
class IFoo
{
public:
virtual void DoFoo() = 0;
};
class Bar : public IFoo
{
char* dooby = NULL;
public:
virtual void DoFoo() { dooby = new char[10]; }
void ~Bar() { delete [] dooby; }
};
IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
มันไม่จำเป็นเสมอไป แต่ฉันคิดว่ามันเป็นการฝึกฝนที่ดี มันทำอะไรได้หรือไม่จะทำให้วัตถุที่ได้รับนั้นถูกลบอย่างปลอดภัยผ่านตัวชี้ประเภทฐาน
ตัวอย่างเช่น:
Base *p = new Derived;
// use p as you see fit
delete p;
เป็นรูปแบบที่ไม่ดีถ้าBase
ไม่ได้ destructor Base *
เสมือนเพราะมันจะพยายามที่จะลบวัตถุที่เป็นว่ามันเป็น
shared_ptr
จะพยายามลบวัตถุราวกับว่ามันเป็นBase *
- มันจำประเภทของสิ่งที่คุณสร้างขึ้นด้วย ดูลิงก์อ้างอิงโดยเฉพาะอย่างยิ่งบิตที่ระบุว่า "destructor จะเรียกการลบด้วยตัวชี้เดียวกันพร้อมด้วยประเภทดั้งเดิมแม้ว่า T จะไม่มี virtual destructor หรือเป็นโมฆะ"
ไม่ใช่แค่การฝึกฝนที่ดีเท่านั้น มันเป็นกฎ # 1 สำหรับลำดับชั้นใด ๆ
ตอนนี้สำหรับทำไม ใช้ลำดับชั้นสัตว์ทั่วไป destructors เสมือนต้องผ่านการจัดส่งเสมือนเช่นเดียวกับการเรียกใช้เมธอดอื่น ๆ นำตัวอย่างต่อไปนี้
Animal* pAnimal = GetAnimal();
delete pAnimal;
สมมติว่า Animal เป็นคลาสนามธรรม วิธีเดียวที่ C ++ รู้ว่า destructor ที่เหมาะสมในการโทรคือผ่านการจัดส่งเมธอดเสมือน ถ้า destructor ไม่ใช่เสมือนจริงมันก็จะเรียก Animal destructor และไม่ทำลายวัตถุใด ๆ ในคลาสที่ได้รับมา
เหตุผลในการสร้าง destructor เสมือนในคลาสพื้นฐานคือมันเพียงลบตัวเลือกจากคลาสที่ได้รับ destructor ของพวกเขากลายเป็นเสมือนจริงโดยค่าเริ่มต้น
คำตอบนั้นง่ายคุณต้องเป็นเสมือนมิฉะนั้นคลาสฐานจะไม่เป็นคลาส polymorphic ที่สมบูรณ์
Base *ptr = new Derived();
delete ptr; // Here the call order of destructors: first Derived then Base.
คุณต้องการลบข้างต้น แต่ถ้า destructor ของคลาสพื้นฐานไม่ใช่เสมือนเฉพาะ destructor ของคลาสพื้นฐานเท่านั้นที่จะถูกเรียกและข้อมูลทั้งหมดในคลาสที่ได้รับจะยังไม่ถูกลบ
delete p
เรียกใช้พฤติกรรมที่ไม่ได้กำหนดInterface::~Interface
มันไม่ได้รับประกันว่าจะโทร