原文链接:

OOP52-CPP. Do not delete a polymorphic object without a virtual destructor


C++ 标准, [expr.delete], 段 3 [ISO/IEC 14882-2014], 陈述如下:

In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.

不要通过一个指向无virtual析构函数的基类类型的指针来删除一个派生类对象。反而,应该定义带有虚构函数的基类。通过一个指向无visual析构函数的基类类型的指针来删除一个派生类对象会导致 未定义行为,undefined behavior.

不兼容代码示例

在这个不兼容例子中,b 是一个多态指针类型,其静态类型是 Base *,动态类型是 Derived *。当 b 被删除时,它将导致 未定义行为,undefined behavior,因为 Base 没有 vritual 析构函数。 C++ 标准, [class.dtor], 段 4 [ISO/IEC 14882-2014], 陈述如下:

If a class has no user-declared destructor, a destructor is implicitly declared as defaulted. An implicitly declared destructor is an inline public member of its class.

隐式声明的析构函数不会被声明为 virtual 即使存在其他的 virtual 函数.

1
2
3
4
5
6
7
8
9
10
11
struct Base {
virtual void f();
};

struct Derived : Base {};

void f() {
Base *b = new Derived();
// ...
delete b;
}

不兼容代码示例

在这个不兼容例子中,显式指针操作已经被替换为指针指针对象,演示了智能指针也遭遇了其他指针相同的问题。因为 std::unique_ptr 默认删除器对内部指针值调用了 delete ,后果和先前不兼容的示例一样。

1
2
3
4
5
6
7
8
9
10
11
#include <memory>

struct Base {
virtual void f();
};

struct Derived : Base {};

void f() {
std::unique_ptr<Base> b = std::make_unique<Derived()>();
}

兼容方案

在这个兼容方案中,Base 的析构函数是显式声明的 virtual 析构函数,确保多态删除操作结果是定义良好的行为。

1
2
3
4
5
6
7
8
9
10
11
12
struct Base {
virtual ~Base() = default;
virtual void f();
};

struct Derived : Base {};

void f() {
Base *b = new Derived();
// ...
delete b;
}

例外

OOP52-CPP:EX0:允许删除一个无虚析构函数的多态对象,如果该对象是引用自指向该类的指针,而不是通过指向该类继承的类指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
public:
// ...
virtual void AddRef() = 0;
virtual void Destroy() = 0;
};

class Derived final : public Base {
public:
// ...
virtual void AddRef() { /* ... */ }
virtual void Destroy() { delete this; }
private:
~Derived() {}
};

注意假如 Derived 没有被标记成 final,那么 delete this 可能实际上引用了一个 Derived 的子类,从而与该规则冲突。

OOP52-CPP:EX1:允许删除一个无虚析构函数的多态对象,如果它的基类有一个析构的 operator delete,通过其他方式解决派生类的析构函数正确调用问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <new>

class Base {
const int whichDerived;

protected:
Base(int whichDerived) : whichDerived(whichDerived) {}

public:
Base() : Base(0) {}
void operator delete(Base *, std::destroying_delete_t);
};

struct Derived1 final : Base {
Derived1() : Base(1) {}
};

struct Derived2 final : Base {
Derived2() : Base(2) {}
};

void Base::operator delete(Base *b, std::destroying_delete_t) {
switch (b->whichDerived) {
case 0:
b->~Base();
break;
case 1:
static_cast<Derived1 *>(b)->~Derived1();
break;
case 2:
static_cast<Derived2 *>(b)->~Derived2();
}
::operator delete(b);
}

void f() {
Base *b = new Derived1();
// ...
delete b;
}

Risk Assessment

Attempting to destruct a polymorphic object that does not have a virtual destructor declared results in undefined behavior. In practice, potential consequences include abnormal program termination and memory leaks.

Rule Severity Likelihood Remediation Cost Priority Level
OOP52-CPP Low Likely Low P9 L2

Automated Detection

Tool Version Checker Description
Astrée 20.10 **non-virtual-public-destructor-in-non-final-class ** Partially checked
Axivion Bauhaus Suite 7.2.0 CertC++-OOP52
Clang 3.9 -Wdelete-non-virtual-dtor
CodeSonar 6.2p0 LANG.STRUCT.DNVD delete with Non-Virtual Destructor
Helix QAC 2022.1 C++3402, C++3403, C++3404
Klocwork 2022.1 CL.MLK.VIRTUAL CWARN.DTOR.NONVIRT.DELETE
LDRA tool suite 9.7.1 303 S** ** Partially implemented
Parasoft C/C++test 2021.2 CERT_CPP-OOP52-a Define a virtual destructor in classes used as base classes which have virtual functions
PRQA QA-C++ 4.4 3402, 3403, 3404
Polyspace Bug Finder R2021b CERT C++: OOP52-CPP Checks for situations when a class has virtual functions but not a virtual destructor (rule partially covered)
PVS-Studio 7.17 V599, V689
RuleChecker 20.10 **non-virtual-public-destructor-in-non-final-class ** Partially checked
SonarQube C/C++ Plugin 4.10 S1235

Search for other vulnerabilities resulting from the violation of this rule on the CERT website.

SEI CERT C++ Coding Standard EXP51-CPP. Do not delete an array through a pointer of the incorrect type

Bibliography

[ISO/IEC 14882-2014] Subclause 5.3.5, “Delete” Subclause 12.4, “Destructors”
[Stroustrup 2006] “Why Are Destructors Not Virtual by Default?”

img img img