什么时候不应该使用虚拟析构函数?

发布于 2024-07-08 18:42:21 字数 53 浏览 14 评论 0原文

是否有充分的理由为类声明虚拟析构函数? 什么时候你应该特别避免写一篇?

Is there ever a good reason to not declare a virtual destructor for a class? When should you specifically avoid writing one?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

日裸衫吸 2024-07-15 18:42:22

我通常将析构函数声明为虚拟的,但如果您有在内循环中使用性能关键的代码,您可能希望避免虚拟表查找。 这在某些情况下可能很重要,例如碰撞检查。 但如果使用继承,请注意如何销毁这些对象,否则只会销毁对象的一半。

请注意,如果该对象上的任何方法都是虚拟的,则该对象会进行虚拟表查找。 因此,如果类中有其他虚拟方法,则删除析构函数上的虚拟规范是没有意义的。

I usually declare the destructor virtual, but if you have performance critical code that is used in an inner loop, you might want to avoid the virtual table lookup. That can be important in some cases, like collision checking. But be careful about how you destroy those objects if you use inheritance, or you will destroy only half of the object.

Note that the virtual table lookup happens for an object if any method on that object is virtual. So no point in removing the virtual specification on a destructor if you have other virtual methods in the class.

热风软妹 2024-07-15 18:42:22

如果你绝对必须确保你的类没有虚函数表,那么你也不能有虚拟析构函数。

这种情况很少见,但确实发生过。

最熟悉的执行此操作的模式示例是 DirectX D3DVECTOR 和 D3DMATRIX 类。 这些是类方法,而不是语法糖的函数,但这些类故意没有 vtable,以避免函数开销,因为这些类专门用于许多高性能应用程序的内部循环。

If you absolutely positively must ensure that your class does not have a vtable then you must not have a virtual destructor as well.

This is a rare case, but it does happen.

The most familiar example of a pattern that does this are the DirectX D3DVECTOR and D3DMATRIX classes. These are class methods instead of functions for the syntactic sugar, but the classes intentionally do not have a vtable in order to avoid the function overhead because these classes are specifically used in the inner loop of many high-performance applications.

宣告ˉ结束 2024-07-15 18:42:22

将在基类上执行并且应该以虚拟方式运行的操作应该是虚拟的。 如果可以通过基类接口以多态方式执行删除,那么它的行为必须是虚拟的并且是虚拟的。

如果您不打算从类派生,则析构函数不需要是虚拟的。 即使您这样做,如果不需要删除基类指针,则受保护的非虚拟析构函数也同样好

On operation that will be performed on the base class, and that should behave virtually, should be virtual. If deletion can be performed polymorphically through the base class interface, then it must behave virtually and be virtual.

The destructor has no need to be virtual if you don't intend to derive from the class. And even if you do, a protected non-virtual destructor is just as good if deletion of base class pointers isn't required.

土豪 2024-07-15 18:42:22

性能答案是我所知道的唯一有可能是真实的答案。 如果您测量并发现对析构函数进行去虚拟化确实可以加快速度,那么您可能在该类中还有其他需要加速的东西,但此时有更重要的考虑因素。 有一天,有人会发现您的代码将为他们提供一个很好的基类,并为他们节省一周的工作。 您最好确保他们完成本周的工作,复制并粘贴您的代码,而不是使用您的代码作为基础。 您最好确保将一些重要的方法设为私有,这样就没有人可以继承您的方法。

The performance answer is the only one I know of which stands a chance of being true. If you've measured and found that de-virtualizing your destructors really speeds things up, then you've probably got other things in that class that need speeding up too, but at this point there are more important considerations. Some day someone is going to discover that your code would provide a nice base class for them and save them a week's work. You'd better make sure they do that week's work, copying and pasting your code, instead of using your code as a base. You'd better make sure you make some of your important methods private so that no one can ever inherit from you.

滥情空心 2024-07-15 18:42:21

当满足以下任何条件时,则无需使用虚拟析构函数:

  • 无意从中派生类
  • 不在堆上实例化
  • 无意通过指向超类的指针进行存储

没有具体理由避免使用它,除非您是记忆力真的太紧张了。

There is no need to use a virtual destructor when any of the below is true:

  • No intention to derive classes from it
  • No instantiation on the heap
  • No intention to store with access via a pointer to a superclass

No specific reason to avoid it unless you are really so pressed for memory.

苍风燃霜 2024-07-15 18:42:21

明确回答这个问题,即什么时候不应该声明虚拟析构函数。

C++ '98/'03

添加虚拟析构函数可能会将您的类从 POD (普通旧数据)* 或聚合到非 POD。 如果您的类类型是在某处聚合初始化的,这可能会阻止您的项目编译。

struct A {
  // virtual ~A ();
  int i;
  int j;
};
void foo () { 
  A a = { 0, 1 };  // Will fail if virtual dtor declared
}

在极端情况下,这样的更改还可能导致未定义的行为,其中类以需要 POD 的方式使用,例如通过省略号参数传递它,或与 memcpy 一起使用它。

void bar (...);
void foo (A & a) { 
  bar (a);  // Undefined behavior if virtual dtor declared
}

[* POD 类型是对其内存布局有特定保证的类型。 该标准实际上只是说,如果您要从 POD 类型的对象复制到字符数组(或无符号字符)并再次复制回来,那么结果将与原始对象相同。]

现代 C++< /strong>

在 C++ 的最新版本中,POD 的概念分为类布局及其构造、复制和销毁。

对于省略号的情况,它不再是未定义的行为,它现在通过实现定义的语义有条件地支持(N3937 - ~C++ '14 - 5.2.2/7):

...有条件地支持传递具有非平凡复制构造函数、非平凡移动构造函数或非平凡析构函数的类类型(第 9 条)的潜在评估参数,且没有相应的参数具有实现定义的语义。

声明 =default 之外的析构函数意味着它并不简单 (12.4/5)

...如果析构函数不是用户提供的,那么它是微不足道的...

现代 C++ 的其他更改减少了聚合初始化问题的影响,因为可以添加构造函数:

struct A {
  A(int i, int j);
  virtual ~A ();
  int i;

  int j;
};
void foo () { 
  A a = { 0, 1 };  // OK
}

To answer the question explicitly, i.e. when should you not declare a virtual destructor.

C++ '98/'03

Adding a virtual destructor might change your class from being POD (plain old data)* or aggregate to non-POD. This can stop your project from compiling if your class type is aggregate initialized somewhere.

struct A {
  // virtual ~A ();
  int i;
  int j;
};
void foo () { 
  A a = { 0, 1 };  // Will fail if virtual dtor declared
}

In an extreme case, such a change can also cause undefined behaviour where the class is being used in a way that requires a POD, e.g. passing it via an ellipsis parameter, or using it with memcpy.

void bar (...);
void foo (A & a) { 
  bar (a);  // Undefined behavior if virtual dtor declared
}

[* A POD type is a type that has specific guarantees about its memory layout. The standard really only says that if you were to copy from an object with POD type into an array of chars (or unsigned chars) and back again, then the result will be the same as the original object.]

Modern C++

In recent versions of C++, the concept of POD was split between the class layout and its construction, copying and destruction.

For the ellipsis case, it is no longer undefined behavior it is now conditionally-supported with implementation-defined semantics (N3937 - ~C++ '14 - 5.2.2/7):

...Passing a potentially-evaluated argument of class type (Clause 9) having a non-trivial copy constructor, a non-trivial move constructor, or a on-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics.

Declaring a destructor other than =default will mean it's not trivial (12.4/5)

... A destructor is trivial if it is not user-provided ...

Other changes to Modern C++ reduce the impact of the aggregate initialization problem as a constructor can be added:

struct A {
  A(int i, int j);
  virtual ~A ();
  int i;

  int j;
};
void foo () { 
  A a = { 0, 1 };  // OK
}
无妨# 2024-07-15 18:42:21

当且仅当我有虚拟方法时,我才声明虚拟析构函数。 一旦我有了虚拟方法,我就不相信自己会避免在堆上实例化它或存储指向基类的指针。 这两种操作都是极其常见的操作,如果析构函数未声明为虚拟,通常会默默地泄漏资源。

I declare a virtual destructor if and only if I have virtual methods. Once I have virtual methods, I don't trust myself to avoid instantiating it on the heap or storing a pointer to the base class. Both of these are extremely common operations and will often leak resources silently if the destructor is not declared virtual.

ゞ花落谁相伴 2024-07-15 18:42:21

每当有可能对指向具有您的类类型的子类的对象的指针调用delete时,就需要虚拟析构函数。 这可以确保在运行时调用正确的析构函数,而编译器不必在编译时知道堆上对象的类。 例如,假设 BA 的子类:

A *x = new B;
delete x;     // ~B() called, even though x has type A*

如果您的代码对性能要求不高,那么向您编写的每个基类添加一个虚拟析构函数是合理的,只需为了安全。

但是,如果您发现自己在紧密循环中删除大量对象,则调用虚拟函数(即使是空函数)的性能开销可能会很明显。 编译器通常无法内联这些调用,并且处理器可能很难预测去哪里。 这不太可能对性能产生重大影响,但值得一提。

A virtual destructor is needed whenever there is any chance that delete might be called on a pointer to an object of a subclass with the type of your class. This makes sure the correct destructor gets called at run time without the compiler having to know the class of an object on the heap at compile time. For example, assume B is a subclass of A:

A *x = new B;
delete x;     // ~B() called, even though x has type A*

If your code is not performance critical, it would be reasonable to add a virtual destructor to every base class you write, just for safety.

However, if you found yourself deleteing a lot of objects in a tight loop, the performance overhead of calling a virtual function (even one that's empty) might be noticeable. The compiler cannot usually inline these calls, and the processor might have a difficult time predicting where to go. It is unlikely this would have a significant impact on performance, but it's worth mentioning.

反目相谮 2024-07-15 18:42:21

虚函数意味着每个分配的对象都会通过虚函数表指针增加内存成本。

因此,如果您的程序涉及分配大量的某个对象,则值得避免所有虚拟函数,以便为每个对象节省额外的 32 位。

在所有其他情况下,您可以通过使 dtor 虚拟化来避免调试的痛苦。

Virtual functions mean every allocated object increases in memory cost by a virtual function table pointer.

So if your program involves allocating a very large number of some object, it would be worth avoiding all virtual functions in order to save the additional 32 bits per object.

In all other cases, you will save yourself debug misery to make the dtor virtual.

柒七 2024-07-15 18:42:21

并非所有 C++ 类都适合用作具有动态多态性的基类。

如果你希望你的类适合动态多态,那么它的析构函数必须是虚拟的。 此外,子类可能想要重写的任何方法(这可能意味着所有公共方法,加上可能内部使用的一些受保护方法)都必须是虚拟的。

如果你的类不适合动态多态性,那么析构函数不应该被标记为virtual,因为这样做会产生误导。 它只会鼓励人们错误地使用你的课程。

下面是一个不适合动态多态性的类的示例,即使其析构函数是虚拟的:

class MutexLock {
    mutex *mtx_;
public:
    explicit MutexLock(mutex *mtx) : mtx_(mtx) { mtx_->lock(); }
    ~MutexLock() { mtx_->unlock(); }
private:
    MutexLock(const MutexLock &rhs);
    MutexLock &operator=(const MutexLock &rhs);
};

该类的全部要点是位于 RAII 的堆栈上。 如果您传递指向此类对象的指针,更不用说它的子类了,那么您就做错了。

Not all C++ classes are suitable for use as a base class with dynamic polymorphism.

If you want your class to be suitable for dynamic polymorphism, then its destructor must be virtual. In addition, any methods which a subclass could conceivably want to override (which might mean all public methods, plus potentially some protected ones used internally) must be virtual.

If your class is not suitable for dynamic polymorphism, then the destructor should not be marked virtual, because to do so is misleading. It just encourages people to use your class incorrectly.

Here's an example of a class which would not be suitable for dynamic polymorphism, even if its destructor were virtual:

class MutexLock {
    mutex *mtx_;
public:
    explicit MutexLock(mutex *mtx) : mtx_(mtx) { mtx_->lock(); }
    ~MutexLock() { mtx_->unlock(); }
private:
    MutexLock(const MutexLock &rhs);
    MutexLock &operator=(const MutexLock &rhs);
};

The whole point of this class is to sit on the stack for RAII. If you're passing around pointers to objects of this class, let alone subclasses of it, then you're Doing It Wrong.

笑咖 2024-07-15 18:42:21

不将析构函数声明为虚拟的一个很好的理由是,这可以使您的类免于添加虚拟函数表,并且您应该尽可能避免这种情况。

我知道很多人更喜欢总是将析构函数声明为虚拟的,只是为了安全起见。 但是,如果您的类没有任何其他虚拟函数,那么拥有虚拟析构函数确实没有任何意义。 即使您将您的类提供给其他人,然后他们从中派生其他类,那么他们也没有理由对向上转换为您的类的指针调用delete - 如果他们这样做,那么我会认为这是一个错误。

好吧,有一个例外,即如果您的类(错误)用于执行派生对象的多态删除,但您 - 或其他人 - 希望知道这需要一个虚拟析构函数。

换句话说,如果你的类有一个非虚拟析构函数,那么这是一个非常明确的声明:“不要用我来删除派生对象!”

A good reason for not declaring a destructor as virtual is when this saves your class from having a virtual function table added, and you should avoid that whenever possible.

I know that many people prefer to just always declare destructors as virtual, just to be on the safe side. But if your class does not have any other virtual functions then there is really, really no point in having a virtual destructor. Even if you give your class to other people who then derive other classes from it then they would have no reason to ever call delete on a pointer that was upcast to your class - and if they do then I would consider this a bug.

Okay, there is one single exception, namely if your class is (mis-)used to perform polymorphic deletion of derived objects, but then you - or the other guys - hopefully know that this requires a virtual destructor.

Put another way, if your class has a non-virtual destructor then this is a very clear statement: "Don't use me for deleting derived objects!"

追我者格杀勿论 2024-07-15 18:42:21

如果您有一个非常小的类,但有大量实例,则 vtable 指针的开销可能会影响程序的内存使用情况。 只要您的类没有任何其他虚拟方法,使析构函数成为非虚拟就可以节省开销。

If you have a very small class with a huge number of instances, the overhead of a vtable pointer can make a difference in your program's memory usage. As long as your class doesn't have any other virtual methods, making the destructor non-virtual will save that overhead.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文