ทำไมเราต้องมี destructor เสมือนแท้ใน C ++


154

ฉันเข้าใจถึงความต้องการตัวทำลายล้างเสมือนจริง แต่ทำไมเราต้องมี destructor เสมือนแท้? ในหนึ่งในบทความ C ++ ผู้เขียนได้กล่าวว่าเราใช้ destructor เสมือนจริงเมื่อเราต้องการสร้างบทคัดย่อของคลาส

แต่เราสามารถสร้างคลาสนามธรรมโดยการทำให้ฟังก์ชั่นสมาชิกใด ๆ เสมือนเสมือนจริง

ดังนั้นคำถามของฉันคือ

  1. เมื่อไหร่ที่เราจะสร้างผู้ทำลายล้างเสมือนจริง? ใครสามารถยกตัวอย่างเรียลไทม์ได้บ้าง?

  2. เมื่อเราสร้างคลาสนามธรรมมันเป็นวิธีปฏิบัติที่ดีที่จะทำให้ destructor นั้นเสมือนจริงหรือไม่? ถ้าใช่ .. แล้วทำไมล่ะ


4
รายการซ้ำหลายรายการ: stackoverflow.com/questions/999340/…และstackoverflow.com/questions/630950/pure-virtual-destructor-in-cเป็นสองคน
Daniel Sloof

14
@ Daniel- ลิงก์ดังกล่าวไม่ตอบคำถามของฉัน มันตอบว่าทำไม destructor เสมือนแท้ควรมีคำจำกัดความ คำถามของฉันคือเหตุผลที่เราต้องการตัวทำลายเสมือนที่แท้จริง
Mark

ฉันพยายามหาเหตุผล แต่คุณถามคำถามที่นี่แล้ว
nsivakr

คำตอบ:


119
  1. อาจเป็นเหตุผลที่แท้จริงที่อนุญาตให้มี destructors เสมือนบริสุทธิ์ที่จะห้ามพวกเขาจะหมายถึงการเพิ่มกฎอื่นให้กับภาษาและไม่จำเป็นต้องมีกฎนี้เนื่องจากไม่มีผลกระทบที่ไม่ดีสามารถทำให้ destructor เสมือนบริสุทธิ์

  2. Nope, virtual old ธรรมดาก็เพียงพอแล้ว

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

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

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

หมายเหตุ: destructor เป็นวิธีเดียวที่ถึงแม้ว่ามันจะเป็นเสมือนบริสุทธิ์มีที่จะมีการดำเนินงานในการสั่งซื้ออินสแตนซ์ที่ได้รับการเรียน (หน้าที่เสมือนบริสุทธิ์ใช่จะมีการใช้งาน)

struct foo {
    virtual void bar() = 0;
};

void foo::bar() { /* default implementation */ }

class foof : public foo {
    void bar() { foo::bar(); } // have to explicitly call default implementation.
};

13
"ใช่ฟังก์ชั่นเสมือนที่บริสุทธิ์สามารถมีการใช้งานได้" จากนั้นมันก็ไม่ได้เสมือนจริง
GManNickG

2
ถ้าคุณต้องการสร้างคลาสของบทคัดย่อมันจะง่ายกว่าหรือไม่ที่จะป้องกันการสร้างทั้งหมด
bdonlan

78
@GMan คุณเข้าใจผิดว่าเป็นวิธีเสมือนจริงที่บริสุทธิ์ชั้นเรียนที่ได้มาจะต้องแทนที่วิธีนี้นี่คือมุมฉากที่จะมีการใช้งาน ลองดูรหัสของฉันและแสดงความคิดเห็นfoof::barหากคุณต้องการดูด้วยตัวคุณเอง
Motti

15
@GMan: คำถามที่พบบ่อย C ++ lite กล่าวว่า "โปรดทราบว่ามันเป็นไปได้ที่จะให้คำจำกัดความสำหรับฟังก์ชั่นเสมือนที่บริสุทธิ์ แต่โดยปกติแล้วมันจะทำให้เกิดความสับสนสามเณร parashift.com/c++-faq-lite/abcs.html#faq-22.4 Wikipedia (ป้อมปราการแห่งความถูกต้องนั้น) ก็กล่าวเช่นเดียวกัน ฉันเชื่อว่ามาตรฐาน ISO / IEC ใช้คำศัพท์ที่คล้ายกัน (น่าเสียดายที่สำเนาของฉันทำงานอยู่ในขณะนี้) ... ฉันยอมรับว่ามันทำให้เกิดความสับสนและโดยทั่วไปฉันจะไม่ใช้คำนั้นโดยไม่ต้องชี้แจงเมื่อฉันให้คำจำกัดความโดยเฉพาะ โปรแกรมเมอร์รอบใหม่ ...
Leander

9
@Motti: สิ่งที่น่าสนใจที่นี่และให้ความสับสนมากขึ้นคือ destructor เสมือนแท้ไม่จำเป็นต้องมีการอธิบายอย่างชัดเจนในคลาสที่ได้รับ (และอินสแตนซ์) ในกรณีเช่นนี้มีการใช้คำนิยามโดยนัย :)
kappa

33

สิ่งที่คุณต้องการสำหรับคลาสนามธรรมคือฟังก์ชันเสมือนบริสุทธิ์อย่างน้อยหนึ่งฟังก์ชัน ฟังก์ชั่นใด ๆ ที่จะทำ; แต่มันเกิดขึ้น destructor เป็นสิ่งที่ใด ๆในชั้นเรียนจะมีให้ก็มักจะมีเป็นผู้สมัคร นอกจากนี้การทำให้ destructor pure virtual (ตรงกันข้ามกับ virtual virtual) นั้นไม่มีผลข้างเคียงเชิงพฤติกรรมที่นอกเหนือจากการทำให้นามธรรมเป็นคลาส ดังนั้นคำแนะนำรูปแบบจำนวนมากแนะนำให้ใช้ destuctor เสมือนบริสุทธิ์อย่างสม่ำเสมอเพื่อระบุว่าคลาสนั้นเป็นนามธรรม - หากไม่มีเหตุผลอื่นนอกจากจะให้สถานที่ที่สอดคล้องกันคนที่อ่านรหัสสามารถดูได้ว่าคลาสนั้นเป็นนามธรรมหรือไม่


1
แต่ก็ยังเป็นเหตุผลที่จะให้การใช้งานตัวทำลายล้างบริสุทธิ์ มีอะไรที่ผิดพลาดไปบ้างที่ฉันสร้าง destructor pure virtual และไม่ได้ให้การใช้งาน ฉันสมมติว่ามีการประกาศพอยน์เตอร์ของคลาสพื้นฐานเท่านั้นดังนั้นตัวทำลายสำหรับคลาสนามธรรมจะไม่ถูกเรียก
กฤษณะ Oza

4
@ Surfing: เนื่องจาก destructor ของคลาสที่ได้รับโดยปริยายเรียกว่า destructor ของคลาสพื้นฐานของมันแม้ว่า destructor นั้นจะเป็นเสมือนจริงก็ตาม ดังนั้นหากไม่มีการดำเนินการใด ๆ มันจะไม่เกิดขึ้น
a.peganz

19

หากคุณต้องการสร้างคลาสฐานนามธรรม:

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

... มันง่ายที่สุดในการสร้างคลาสนามธรรมโดยการทำให้ destructor บริสุทธิ์เสมือนและให้คำจำกัดความ (เนื้อความของเมธอด)

สำหรับ ABC เบื้องต้นของเรา:

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


8

จากคำตอบที่ฉันได้อ่านกับคำถามของคุณฉันไม่สามารถอนุมานเหตุผลที่ดีในการใช้ destructor เสมือนจริงได้ ตัวอย่างเช่นเหตุผลต่อไปนี้ไม่ทำให้ฉันมั่นใจเลย:

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

ในความคิดของฉัน destructors เสมือนบริสุทธิ์จะมีประโยชน์ ตัวอย่างเช่นสมมติว่าคุณมีสองคลาส myClassA และ myClassB ในรหัสของคุณและ myClassB สืบทอดจาก myClassA สำหรับเหตุผลที่สกอตต์เมเยอร์สกล่าวไว้ในหนังสือของเขา "More Effective C ++", รายการที่ 33 "การสร้างคลาสที่ไม่เป็นนามธรรม" มันเป็นวิธีปฏิบัติที่ดีกว่าในการสร้างคลาสนามธรรม myAbstractClass ที่ myClassA และ myClassB สืบทอดมา สิ่งนี้ให้นามธรรมที่ดีขึ้นและป้องกันปัญหาบางอย่างที่เกิดขึ้นเช่นคัดลอกวัตถุ

ในกระบวนการ abstraction (ของการสร้างคลาส myAbstractClass) อาจเป็นไปได้ว่าไม่มีเมธอดของ myClassA หรือ myClassB เป็นตัวเลือกที่ดีสำหรับการเป็นวิธีเสมือนจริงที่บริสุทธิ์ ในกรณีนี้คุณกำหนด destructor pure virtual class ของ abstract

ต่อไปนี้เป็นตัวอย่างที่เป็นรูปธรรมจากรหัสบางอย่างที่ฉันเขียนเอง ฉันมีสองคลาสคือ Numerics / PhysicsParams ซึ่งแบ่งปันคุณสมบัติทั่วไป ฉันปล่อยให้พวกเขาสืบทอดจาก IParams ระดับนามธรรม ในกรณีนี้ฉันไม่มีวิธีใดที่สามารถเป็นเสมือนได้อย่างแท้จริง ตัวอย่างเช่นเมธอด setParameter ต้องมีเนื้อความเดียวกันสำหรับทุกคลาสย่อย ทางเลือกเดียวที่ฉันมีคือทำให้ตัวทำลายล้างของ IParams เสมือนจริงอย่างแท้จริง

struct IParams
{
    IParams(const ModelConfiguration& aModelConf);
    virtual ~IParams() = 0;

    void setParameter(const N_Configuration::Parameter& aParam);

    std::map<std::string, std::string> m_Parameters;
};

struct NumericsParams : IParams
{
    NumericsParams(const ModelConfiguration& aNumericsConf);
    virtual ~NumericsParams();

    double dt() const;
    double ti() const;
    double tf() const;
};

struct PhysicsParams : IParams
{
    PhysicsParams(const N_Configuration::ModelConfiguration& aPhysicsConf);
    virtual ~PhysicsParams();

    double g()     const; 
    double rho_i() const; 
    double rho_w() const; 
};

1
ฉันชอบการใช้งานนี้ แต่อีกวิธีหนึ่งในการ "บังคับใช้" การสืบทอดคือการประกาศตัวสร้างของที่IParamจะได้รับการปกป้องตามที่ระบุไว้ในความคิดเห็นอื่น
rwols

4

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


3

ที่นี่ฉันต้องการบอกเมื่อเราต้องการdestructor เสมือนและเมื่อเราต้องการdestructor เสมือนแท้

class Base
{
public:
    Base();
    virtual ~Base() = 0; // Pure virtual, now no one can create the Base Object directly 
};

Base::Base() { cout << "Base Constructor" << endl; }
Base::~Base() { cout << "Base Destructor" << endl; }


class Derived : public Base
{
public:
    Derived();
    ~Derived();
};

Derived::Derived() { cout << "Derived Constructor" << endl; }
Derived::~Derived() {   cout << "Derived Destructor" << endl; }


int _tmain(int argc, _TCHAR* argv[])
{
    Base* pBase = new Derived();
    delete pBase;

    Base* pBase2 = new Base(); // Error 1   error C2259: 'Base' : cannot instantiate abstract class
}
  1. เมื่อคุณต้องการที่ไม่มีใครควรจะสามารถที่จะสร้างวัตถุของคลาสฐานโดยตรงใช้ virtual ~Base() = 0destructor โดยปกติแล้วจะต้องใช้ฟังก์ชันเสมือนบริสุทธิ์อย่างน้อยหนึ่งฟังก์ชันลองมา virtual ~Base() = 0ฟังก์ชั่นนี้

  2. เมื่อคุณไม่ต้องการสิ่งที่เหนือกว่าเพียงคุณเท่านั้นที่ต้องการทำลายวัตถุคลาสที่ได้รับความปลอดภัย

    ฐาน * pBase = ใหม่ที่ได้รับ (); ลบ pBase; ไม่จำเป็นต้องใช้ virtual destructor เพียง destructor เสมือนเท่านั้นที่ทำงานได้


2

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

ความสัมพันธ์พื้นฐานของการออกแบบเชิงวัตถุคือสอง: IS-A และ HAS-A ฉันไม่ได้ทำสิ่งเหล่านั้นขึ้นมา นั่นคือสิ่งที่พวกเขาถูกเรียก

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

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

แนวคิดทั้งสองนี้ง่ายต่อการรับรู้ในภาษาที่สืบทอดเดียวมากกว่าในรูปแบบการสืบทอดหลายอย่างเช่น c ++ แต่กฎเป็นเหมือนกัน ภาวะแทรกซ้อนเกิดขึ้นเมื่อเอกลักษณ์ของคลาสไม่ชัดเจนเช่นการส่งตัวชี้คลาสกล้วยไปยังฟังก์ชันที่ใช้ตัวชี้คลาสผลไม้

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

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

คลาส Fruit อาจมีสีฟังก์ชันเสมือน () ที่ส่งคืน "NONE" เป็นค่าเริ่มต้น ฟังก์ชันคลาส Banana () สีคืนค่า "YELLOW" หรือ "BROWN"

แต่ถ้าฟังก์ชั่นการใช้ตัวชี้ผลไม้เรียกสี () บนคลาส Banana ที่ส่งไป - ฟังก์ชัน color () ใดที่ถูกเรียกใช้? ฟังก์ชั่นปกติจะเรียกผลไม้ :: สี () สำหรับวัตถุผลไม้

นั่นจะ 99% ของเวลาไม่ใช่สิ่งที่ตั้งใจไว้ แต่ถ้า Fruit :: color () ถูกประกาศเสมือนแล้ว Banana: color () จะถูกเรียกใช้สำหรับวัตถุเนื่องจากฟังก์ชัน color () ที่ถูกต้องจะถูกผูกไว้กับตัวชี้ Fruit ในขณะที่มีการโทร รันไทม์จะตรวจสอบวัตถุที่ตัวชี้ชี้ไปเนื่องจากถูกทำเครื่องหมายเสมือนในนิยามคลาสผลไม้

สิ่งนี้แตกต่างจากการแทนที่ฟังก์ชันในคลาสย่อย ในกรณีนั้นตัวชี้ผลไม้จะเรียกผลไม้ :: สี () ถ้าทั้งหมดรู้ก็คือมันเป็นตัวชี้ไปที่ผลไม้

ดังนั้นแนวคิดของ "ฟังก์ชั่นเสมือนจริง" จึงเกิดขึ้น มันเป็นวลีที่ค่อนข้างโชคร้ายเนื่องจากความบริสุทธิ์ไม่มีส่วนเกี่ยวข้องกับมัน หมายความว่ามันมีจุดมุ่งหมายที่จะไม่เรียกวิธีการคลาสพื้นฐาน อันที่จริงไม่สามารถเรียกใช้ฟังก์ชันเสมือนบริสุทธิ์ได้ อย่างไรก็ตามจะต้องมีการกำหนดไว้ ต้องมีลายเซ็นฟังก์ชัน ผู้เขียนโค้ดจำนวนมากทำการใช้งานที่ว่าง {} เพื่อความสมบูรณ์ แต่คอมไพเลอร์จะสร้างหนึ่งภายในหากไม่ได้ ในกรณีนั้นเมื่อฟังก์ชั่นถูกเรียกแม้ว่าตัวชี้ไปที่ Fruit, Banana :: color () จะถูกเรียกใช้เนื่องจากเป็นการใช้สีเท่านั้น () ที่มี

ตอนนี้ชิ้นส่วนสุดท้ายของจิ๊กซอว์: ผู้สร้างและผู้ทำลายล้าง

Constructor เสมือนล้วนผิดกฎหมายอย่างสมบูรณ์ นั่นเพิ่งจะออกมา

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

 virtual ~Fruit() = 0;  // pure virtual 
 Fruit::~Fruit(){}      // destructor implementation

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

ดังนั้นคุณจะถูกห้ามในกรณีนี้เพื่อสร้างอินสแตนซ์ของ Fruit แต่ได้รับอนุญาตให้สร้างอินสแตนซ์ของ Banana

การเรียกการลบตัวชี้ผลไม้ที่ชี้ไปยังอินสแตนซ์ของ Banana จะเรียก Banana :: ~ Banana () ก่อนแล้วจึงเรียก Fuit :: ~ Fruit () เสมอ เนื่องจากไม่ว่าจะเกิดอะไรขึ้นเมื่อคุณเรียก subclass destructor ตัวทำลายคลาสพื้นฐานต้องปฏิบัติตาม

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

ถ้าคุณเขียน C ++ เพื่อให้คุณส่งผ่านพอยน์เตอร์พอยน์เตอร์ที่แน่นอนโดยไม่ต้องพอยน์เตอร์ทั่วไปหรือคลุมเครือดังนั้นฟังก์ชันเสมือนไม่จำเป็น แต่ถ้าคุณต้องการความยืดหยุ่นในการใช้งานประเภท (เช่นใน Apple Banana Orange ==> ผลไม้) ฟังก์ชั่นจะง่ายขึ้นและหลากหลายมากขึ้นด้วยรหัสซ้ำซ้อนน้อยลง คุณไม่จำเป็นต้องเขียนฟังก์ชันสำหรับผลไม้แต่ละชนิดอีกต่อไปและคุณรู้ว่าผลไม้ทุกชนิดจะตอบสนองต่อสี () ด้วยฟังก์ชั่นที่ถูกต้องของตัวเอง

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


1

นี่คือหัวข้อเก่าทศวรรษที่ผ่านมา :) อ่าน 5 ย่อหน้าสุดท้ายของรายการ # 7 ในหนังสือ "Effective C ++" เพื่อดูรายละเอียดเริ่มจาก "บางครั้งมันอาจจะสะดวกในการให้คลาส destructor เสมือนจริงที่บริสุทธิ์ ... "


0

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

ฉันไม่ต้องการให้ใครสามารถโยนerror_baseประเภท แต่ยกเว้นประเภทerror_oh_shucksและerror_oh_blastมีฟังก์ชั่นที่เหมือนกันและฉันไม่ต้องการที่จะเขียนสองครั้ง ความซับซ้อนของ pImpl นั้นจำเป็นต่อการหลีกเลี่ยงการเปิดเผยstd::stringต่อลูกค้าของฉันและการใช้std::auto_ptrตัวสร้างสำเนาจำเป็นต้องมี

ส่วนหัวสาธารณะมีข้อกำหนดคุณสมบัติของข้อยกเว้นที่ลูกค้าจะสามารถแยกแยะข้อยกเว้นประเภทต่าง ๆ ที่ห้องสมุดของฉันโยนได้:

// error.h

#include <exception>
#include <memory>

class exception_string;

class error_base : public std::exception {
 public:
  error_base(const char* error_message);
  error_base(const error_base& other);
  virtual ~error_base() = 0; // Not directly usable

  virtual const char* what() const;
 private:
  std::auto_ptr<exception_string> error_message_;
};

template<class error_type>
class error : public error_base {
 public:
   error(const char* error_message) : error_base(error_message) {}
   error(const error& other) : error_base(other) {}
   ~error() {}
};

// Neither should these classes be usable
class error_oh_shucks { virtual ~error_oh_shucks() = 0; }
class error_oh_blast { virtual ~error_oh_blast() = 0; }

และนี่คือการใช้งานร่วมกัน:

// error.cpp

#include "error.h"
#include "exception_string.h"

error_base::error_base(const char* error_message)
  : error_message_(new exception_string(error_message)) {}

error_base::error_base(const error_base& other)
  : error_message_(new exception_string(other.error_message_->get())) {}

error_base::~error_base() {}

const char* error_base::what() const {
  return error_message_->get();
}

คลาส exception_string เก็บไว้เป็นส่วนตัวซ่อน std :: string จากส่วนต่อประสานสาธารณะของฉัน:

// exception_string.h

#include <string>

class exception_string {
 public:
  exception_string(const char* message) : message_(message) {}

  const char* get() const { return message_.c_str(); }
 private:
  std::string message_;
};

รหัสของฉันก็จะพ่นข้อผิดพลาดเป็น:

#include "error.h"

throw error<error_oh_shucks>("That didn't work");

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

// client.cpp

#include <error.h>

try {
} catch (const error<error_oh_shucks>&) {
} catch (const error<error_oh_blast>&) {
}

0

อาจจะมีอีกกรณีการใช้งานจริงของ destructor เสมือนจริงที่ฉันไม่เห็นคำตอบอื่น ๆ :)

ตอนแรกฉันเห็นด้วยกับคำตอบที่ทำเครื่องหมายไว้อย่างสมบูรณ์: เนื่องจากการห้าม destructor เสมือนล้วนๆจะต้องมีกฎพิเศษในการกำหนดภาษา แต่ก็ยังไม่ใช่กรณีการใช้งานที่ Mark กำลังเรียกร้อง :)

ลองจินตนาการถึงสิ่งนี้:

class Printable {
  virtual void print() const = 0;
  // virtual destructor should be here, but not to confuse with another problem
};

และสิ่งที่ชอบ:

class Printer {
  void queDocument(unique_ptr<Printable> doc);
  void printAll();
};

เรามีอินเทอร์เฟซPrintableและ "คอนเทนเนอร์" บางอย่างที่ถืออะไรก็ได้ด้วยอินเทอร์เฟซนี้ ฉันคิดว่าที่นี่ค่อนข้างชัดเจนว่าทำไมprint()วิธีการเป็นเสมือนจริง อาจมีบางส่วน แต่ในกรณีที่ไม่มีการใช้งานเริ่มต้นบริสุทธิ์เสมือนเป็น "การนำไปใช้" ในอุดมคติ (= "ต้องจัดเตรียมโดยคลาสที่สืบทอด)"

และจินตนาการตอนนี้เหมือนกันทุกประการยกเว้นสำหรับการพิมพ์ แต่เพื่อการทำลาย:

class Destroyable {
  virtual ~Destroyable() = 0;
};

และอาจมีภาชนะบรรจุที่คล้ายกัน:

class PostponedDestructor {
  // Queues an object to be destroyed later.
  void queObjectForDestruction(unique_ptr<Destroyable> obj);
  // Destroys all already queued objects.
  void destroyAll();
};

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

ฉันคิดว่ามันไม่ใช่ความคิดที่ยิ่งใหญ่ที่นี่เพียงอธิบายเพิ่มเติมว่า virtuality ที่แท้จริงนั้นมีความสม่ำเสมอ - สำหรับผู้ทำลายล้าง


-2

1) เมื่อคุณต้องการให้คลาสที่ได้รับมาทำการล้างข้อมูล นี่เป็นของหายาก

2) ไม่ แต่คุณต้องการให้เป็นเสมือน


-2

เราจำเป็นต้องทำให้ destructor เสมือน bacause ของความจริงที่ว่าถ้าเราไม่ทำ destructor เสมือนแล้วคอมไพเลอร์จะทำลายเนื้อหาของคลาสฐานเท่านั้น n คลาสที่ได้รับทั้งหมดจะยังคงไม่เปลี่ยนแปลง bacuse compiler จะไม่เรียก destructor ของผู้อื่น ชั้นยกเว้นชั้นฐาน


-1: คำถามไม่เกี่ยวกับสาเหตุที่ destructor ควรเป็นเสมือน
Troubadour

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

คุณถูกต้อง 100% นี่คือและได้รับในอดีตหนึ่งในแหล่งที่มาของการรั่วไหลและล่มในโปรแกรม C ++ ที่สามเท่านั้นที่พยายามทำสิ่งที่มีตัวชี้โมฆะและเกินขอบเขตของอาร์เรย์ ตัวทำลายคลาสพื้นฐานที่ไม่เสมือนจะถูกเรียกใช้บนตัวชี้ทั่วไปโดยข้ามตัวทำลายคลาสย่อยทั้งหมดหากไม่ได้ทำเครื่องหมายเสมือน หากมีวัตถุที่สร้างขึ้นแบบไดนามิกที่เป็นของคลาสย่อยพวกเขาจะไม่ได้รับการกู้คืนโดยตัวทำลายฐานในการเรียกเพื่อลบ คุณกำลังตามล่าอย่างบลูมูร์เรค! (หายากเหมือนกัน)
Chris Reid
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.