簡體   English   中英

C ++是否為純虛擬類創建默認的“構造函數/析構函數/復制構造函數/復制賦值運算符”?

[英]Does C++ create default “Constructor/Destructor/Copy Constructor/Copy assignment operator” for pure virtual class?

C ++編譯器是否為這個“類”生成默認函數,如Constructor / Destructor / Copy-Constructor ...?

class IMyInterface
{
    virtual void MyInterfaceFunction() = 0;
}

我的意思是不可能實例化這個“類”,所以我認為沒有生成默認函數。 否則,人們說你必須使用虛擬析構函數。 這意味着如果我沒有定義析構函數虛擬,它將是默認創建的,而不是虛擬的。

此外,我想知道為純虛擬接口定義虛擬析構函數是否合理,如上所述? (所以這里沒有使用指針或數據,所以不需要破壞任何東西)

謝謝。

是。

沒有措辭要求類可以實例化,以便隱式聲明這些特殊成員函數。

這是有道理的 - 只是因為你無法實例化Base,並不意味着Derived類不想使用這些函數。

struct Base
{
   virtual void foo() = 0;
   int x;
};

struct Derived : Base
{
   Derived() {};         // needs access to Base's trivial implicit ctor
   virtual void foo() {}
};

看到:

  • §12.1/ 5(ctor)
  • §12.8/ 9(移動)
  • §12.8/ 20(副本)

此外,我想知道為純虛擬接口定義虛擬析構函數是否合理,如上所述? (所以這里沒有使用指針或數據,所以不需要破壞任何東西)

它不僅合理,而且值得推薦。 這是因為在虛函數層次結構的情況下,(自動)調用專用類的析構函數也會調用它的基類的所有析構函數。 如果未定義它們,則應該出現鏈接錯誤。

如果在類中定義至少一個虛函數,則還應定義虛擬析構函數。

析構函數可以使用=default定義:

這是一個更正(可編譯)的代碼示例:

class ImyInterface
{
    virtual void myInterfaceFunction() = 0;
    virtual ~ImyInterface() = 0;
}

ImyInterface::~ImyInterface() = default;

此外,我想知道為純虛擬接口定義虛擬析構函數是否合理,如上所述? (所以這里沒有使用指針或數據,所以不需要破壞任何東西)

派生類是否會在析構函數中做任何事情? 你能確定他們永遠不會,即使有人接管開發嗎?

擁有虛擬析構函數的關鍵不在於確保基類被正確破壞,無論如何都會發生。 關鍵是當您使用通用接口時,將調用派生類的析構函數:

struct A {
  virtual ~A() {}
  virtual int f() = 0;
};

class B : public A {
  std::ifstream fh;
public:
  virtual ~B() {}
  virtual int f() { return 42; }
};

std::shared_ptr<A> a = new B;

a超出范圍時,為什么ifstream關閉? 因為析構函數使用虛擬析構函數刪除對象

這解決了關於為抽象基類聲明虛擬析構函數的第二個問題(例如,至少一個成員函數是純虛擬的)。 以下是LLVM clang ++編譯器捕獲潛在問題的真實示例。 Apple Developer為Mac OS X Mavericks操作系統提供的命令行工具版本就是這種情況。

假設您有一組派生類,這些派生類最終具有帶抽象基類的父類來定義公共接口。 然后有必要有一個像vector一樣的存儲容器,故意聲明它存儲指向每個元素的抽象基類的指針。 稍后,按照良好的工程實踐,需要“刪除”容器元素並將內存返回到堆中。 最簡單的方法是逐個遍歷vector元素並對每個元素調用delete操作。

好吧,如果抽象基類沒有將析構函數聲明為虛擬,那么clang ++編譯器會提供關於在抽象類上調用非虛析構函數的友好警告。 請記住,實際上只有派生類是使用operator new從堆中分配的。 繼承關系中的派生類指針類型確實是抽象基類類型(例如,is-a關系)。

如果抽象基類析構函數不是虛擬的,那么如何調用正確的派生類'析構函數來釋放內存? 編譯器最好知道更好(至少可能與C ++ 11有關),並使它成為現實。 如果啟用了-Wall編譯器選項,則至少應出現編譯警告。 但是,更糟糕的是,永遠不會到達派生類析構函數,並且永遠不會將內存返回到堆中。 因此,現在存在內存泄漏,跟蹤和修復可能非常具有挑戰性。 它只需要在抽象基類析構函數聲明中添加一個“虛擬”。

示例代碼:

class abstractBase
{
    public:
       abstractBase() { };
       ~abstractBase() { };

       virtual int foo() = 0;
};


class derived : abstractBase
{
    public:
        derived() { };
        ~derived() { };

        int foo() override { return 42; }
};

//
// Later on, within a file like main.cpp . . .
// (header file includes are assumed to be satisfied)
// 
vector<abstractBase*> v;

for (auto i = 0; i < 1000; i++)
{
    v.push_back(new derived());
}



//
// do other stuff, logic, what not
// 


// 
// heap is running low, release memory from vector v above 
//    
for (auto i = v.begin(); i < v.end(); i++)
{
    delete (*i); // problem is right here, how to find the derived class' destructor?
}

要解決此潛在的內存泄漏,抽象基類必須將其析構函數聲明為虛擬。 不需要其他任何東西。 抽象基類現在變為:

class abstractBase
{
    public:
       abstractBase() { };
       virtual ~abstractBase() { };   // insert virtual right here

       virtual int foo() = 0;
}

請注意,抽象基類當前具有空構造函數和析構函數體。 正如Lightness在上面回答的那樣,編譯器為抽象基類創建了默認構造函數,析構函數和復制構造函數(如果沒有由工程師定義)。 強烈建議您閱讀C ++創建者Bjarne Stroustrup的任何C ++編程語言版本,以獲取有關抽象基類的更多詳細信息。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM