c++ - 默认创建类 `final`还是给它们一个虚拟的析构函数?

标签 c++ c++11 final virtual-destructor

如果将非虚拟析构函数的类用作基类(如果将指针或对基类的引用用于引用子类的实例),则它们是错误的来源。

在C++ 11中添加了final类之后,我想知道设置以下规则是否有意义:

每个类都必须满足以下两个属性之一:

  • 被标记为final(如果尚未(还)要从中继承)
  • 有一个虚拟析构函数(如果它是(或打算)继承)

  • 可能在某些情况下,这两个选项都不有意义,但我想可以将它们视为应仔细记录的异常。

    最佳答案

    可能由于缺少虚拟析构函数而引起的最常见的实际问题是通过指向基类的指针删除了一个对象:

    struct Base { ~Base(); };
    struct Derived : Base { ~Derived(); };
    
    Base* b = new Derived();
    delete b; // Undefined Behaviour
    

    虚拟析构函数还影响释放函数的选择。 vtable的存在也会影响type_iddynamic_cast

    如果您的类没有以这些方式使用,则不需要虚拟析构函数。请注意,此用法是,不是类型的属性,既不是Base类型也不是Derived类型的属性。继承使这种错误成为可能,而仅使用隐式转换。 (使用reinterpret_cast这样的显式转换,如果没有继承,可能会出现类似的问题。)

    通过使用智能指针,可以在许多情况下防止出现此特定问题:类似于unique_ptr的类型可以将对具有虚拟析构函数(*)的基类的转换限制为基类。类似shared_ptr的类型可以存储一个删除器,该删除器适用于删除指向shared_ptr<A>B,即使没有虚拟析构函数也是如此。

    (*)尽管当前的std::unique_ptr规范不包含对转换构造函数模板的检查,但在较早的草案中有所限制,请参见LWG 854。提案N3974引入了checked_delete删除器,该删除器还需要一个虚拟dtor才能进行派生到基本的转换。基本上,您的想法是防止进行以下转换:
    unique_checked_ptr<Base> p(new Derived); // error
    
    unique_checked_ptr<Derived> d(new Derived); // fine
    unique_checked_ptr<Base> b( std::move(d) ); // error
    

    正如N3974所建议的那样,这是一个简单的库扩展。您可以编写自己的checked_delete版本,并将其与std::unique_ptr结合使用。

    OP中的两个建议都可能存在性能缺陷:
  • 将一个类标记为final

  • 这样可以防止空基优化。如果您有一个空的类,则其大小必须仍然> = 1个字节。作为数据成员,因此占用空间。但是,作为基类,允许不占用派生类型的对象的内存的不同区域。例如使用将分配器存储在StdLib容器中。
  • 具有虚拟析构函数

  • 如果该类还没有vtable,则会为每个类引入一个vtable,并为每个对象引入一个vptr(如果编译器无法完全消除它)。物体的破坏会变得更加昂贵,这可能会产生影响,例如因为它不再是微不足道的破坏了。此外,这防止了某些操作并限制了该类型的操作:对象的生命周期及其属性链接到该类型的某些属性,例如可微毁的。
    final防止通过继承扩展类。虽然继承通常是扩展现有类型的最糟糕方法之一(与自由函数和聚合相比),但在某些情况下,继承是最合适的解决方案。 final限制了可以使用该类型进行的操作;我应该这样做有一个非常令人信服的根本原因。人们通常无法想象其他人想要使用您的类型的方式。

    T.C.指出了StdLib中的一个示例:源自std::true_type,类似地,源自std::integral_constant(例如占位符)。在元编程中,我们通常不关心多态性和动态存储持续时间。公共(public)继承通常只是实现元功能的最简单方法。我不知道元函数类型的对象是动态分配的任何情况。如果这些对象是完全创建的,则通常用于标记分派(dispatch),而您将在这些分派(dispatch)中使用临时对象。

    另外,我建议使用静态分析器工具。每当您从没有虚拟析构函数的类中公开派生时,都可能会发出某种警告。注意,在许多情况下,您仍然想从某个基类公开地派生而没有虚拟析构函数。例如干或简单地分离关注点。在那些情况下,通常可以通过注释或实用指示来调整静态分析器,以忽略这种从无虚拟变量类派生的情况。当然,外部库(例如C++标准库)需要有异常(exception)。

    更好的方法是,但更复杂的方法是分析何时删除不带虚拟dtor的A类的对象,其中B类继承自A类(UB的实际来源)。但是,此检查可能并不可靠:删除可能会在与定义B(从A派生)的TU不同的转换单元中进行。它们甚至可以位于单独的库中。

    关于c++ - 默认创建类 `final`还是给它们一个虚拟的析构函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30685629/

    相关文章:

    c++ - 内联函数的局部静态/线程局部变量?

    php - 如何在 PHP 中创建最终变量?

    c++ - 调用子类 QWidget 的方法

    c++ - 缩放 QGraphicsScene 以填充整个 QGraphicsView

    c++ - 为什么以及何时需要转换为 char volatile&?

    c++ - 为什么 std::unique_ptr 比标准指针慢得多……在优化之前?

    c++ - 如何按值传递仅 move 类型(例如 std::unique_ptr)?

    xcode - 为什么当 'official' clang 支持 C++11 thread_local 时,Apple clang 不允许它

    java - 最终变量的理解

    java - 将字符串与在 Java 中声明为 final 的 == 进行比较