【问题标题】:Does C++ create default "Constructor/Destructor/Copy Constructor/Copy assignment operator" for pure virtual class?C++ 是否为纯虚拟类创建默认的“构造函数/析构函数/复制构造函数/复制赋值运算符”?
【发布时间】:2014-02-13 22:19:45
【问题描述】:

C++ 编译器是否为这个“类”生成默认函数,如构造函数/析构函数/复制构造函数...?

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

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

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

谢谢。

【问题讨论】:

  • = 0; 只允许跟随虚函数。
  • @billz and Lightness Races in Orbit:已修复

标签: c++ interface pure-virtual


【解决方案1】:

这解决了关于为抽象基类声明虚拟析构函数的第二个问题(例如,至少一个成员函数是纯虚拟的)。这是 LLVM clang++ 编译器捕获潜在问题的真实示例。这发生在 Apple Developer 为 Mac OS X Mavericks 操作系统提供的命令行工具版本中。

假设您有一个派生类的集合,这些派生类最终具有带有抽象基类的父类来定义公共接口。然后有必要有一个像向量这样的存储容器,它被有意声明为存储指向每个元素的抽象基类的指针。稍后,遵循良好的工程实践,需要“删除”容器元素并将内存返回到堆中。最简单的方法是逐个元素遍历向量并在每个元素上调用删除操作。

好吧,如果抽象基类没有将析构函数声明为虚拟,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++ 编程语言版本,以了解有关抽象基类的更多详细信息。

【讨论】:

    【解决方案2】:

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

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

    拥有虚拟析构函数的全部意义在于确保基类被正确析构,无论如何都会发生。关键是在使用泛型接口时会调用派生类的析构函数:

    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】:

      是的。

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

      这是有道理的——仅仅因为你不能实例化 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(副本)

      【讨论】:

      • “不想使用这些函数”当然是一种非常温和的表达方式——我看不出构造函数和析构函数有什么办法可以避免它。毕竟,基类构造函数在派生类构造函数的主体进入之前被调用,反之亦然。
      • @ChristopherCreutzig:确实。
      【解决方案4】:

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

      不仅合理,而且值得推荐。这是因为在虚函数层次结构的情况下,(自动)调用专用类的析构函数也会调用其基类的所有析构函数。如果它们没有被定义,你应该会得到一个链接错误。

      如果你在你的类中至少定义了一个虚函数,你还应该定义一个虚析构函数。

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

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

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

      【讨论】:

      • 定义需要放在.cpp 文件中,否则你应该添加inline 关键字。另外,当另一个成员已经将类标记为抽象时,将析构函数定义为纯虚拟有什么意义?
      猜你喜欢
      • 2013-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-14
      • 2015-01-04
      • 2013-09-28
      • 1970-01-01
      • 2011-07-19
      相关资源
      最近更新 更多