C ++是否为纯虚拟类创建默认的“构造函数/析构函数/复制构造函数/复制赋值运算符”?

时间:2014-01-23 11:33:48

标签: c++ interface pure-virtual

C ++编译器是否为这个“类”生成了Constructor / Destructor / Copy-Constructor等默认函数?

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

我的意思是不可能实例化这个“类”,所以我认为没有生成默认函数。 否则,人们说你必须使用虚拟析构函数。 这意味着如果我没有定义析构函数虚拟,它将是默认创建的,而不是虚拟的。

此外,我想知道为纯虚拟接口定义虚拟析构函数是否合理,如上所述? (所以这里没有使用指针或数据,因此不必破坏任何东西)

感谢。

4 个答案:

答案 0 :(得分:3)

没有措辞要求类可以实例化,以便隐式声明这些特殊成员函数。

这是有道理的 - 只是因为你无法实例化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(副本)

答案 1 :(得分:1)

  

此外,我想知道为纯虚拟接口定义虚拟析构函数是否合理,如上所述? (所以这里没有使用指针或数据,因此不必破坏任何东西)

这不仅是合理的,还是推荐的。这是因为在虚函数层次结构的情况下,(自动)调用专用类的析构函数也会调用它的基类的所有析构函数。如果未定义它们,则应该出现链接错误。

如果在类中定义至少一个虚函数,则还应定义虚拟析构函数。

析构函数可以使用=default来定义:

这是一个更正(可编译)的代码示例:

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

ImyInterface::~ImyInterface() = default;

答案 2 :(得分:1)

  

此外,我想知道为纯虚拟接口定义虚拟析构函数是否合理,如上所述? (所以这里没有使用指针或数据,因此不必破坏任何东西)

衍生类是否会在析构函数中做任何事情?你能确定他们永远不会,即使其他人接管了开发吗?

拥有虚拟析构函数的关键不在于确保基类被正确破坏,无论如何都会发生。关键是当您使用通用接口时,将调用派生类的析构函数:

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已关闭?因为析构函数使用虚析构函数删除对象

答案 3 :(得分:0)

这解决了关于为抽象基类声明虚拟析构函数的第二个问题(例如,至少一个成员函数是纯虚拟的)。以下是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 ++编程语言版本,以获取有关抽象基类的更多详细信息。