【问题标题】:Is destructor a normal function call?析构函数是正常的函数调用吗?
【发布时间】:2015-12-09 14:30:02
【问题描述】:

假设我有两个像这样带有非虚拟析构函数的简单类:

struct A
{
    ~A() { std::cout << "A destructor" << std::endl; }
}
struct B : A
{
    ~B() { std::cout << "B destructor" << std::endl; }
}

B 的实例被析构时,A 的析构函数也会被调用。当我通过A* 类型的指针破坏B 的实例时,将不会调用Bs 析构函数。这是否也算作以调用普通成员函数的方式显式调用析构函数?

struct A
{
    ~A() { std::cout << "Hello" << std::endl; }
    void f() { std::cout << "Hello" << std::endl; }
}

A a;
a.~A(); // case 1
a.f(); // case 2

在这个例子中,除了被调用的函数名之外,这两种情况有什么区别吗?

编辑:考虑与 CRTP 相同的示例:

template <typename C>
struct A
{
    ~A() { static_cast<C*>(this)->~C(); }
}
struct B : public A<B>
{
    ~B() { std::cout << "B destructor" << std::endl; }
}

A<B>* a = new B();
delete a;

这会导致未定义的行为或内存泄漏吗?

【问题讨论】:

  • When I destruct an instance of B through a pointer of type A* 那么你的程序表现出未定义的行为。
  • a.~A(); 本身是合法的,但是 a) 它结束了 a 的生命周期,因此在此之后调用 a.f() 会表现出未定义的行为,并且 b) 最终 a 将退出作用域和析构函数将被再次调用——这也表现出未定义的行为。长话短说,虽然可以显式调用析构函数,但这并不是一个好主意。
  • @IgorTandetnik 这是个好主意吗?
  • 建议将基类的析构函数定义为虚拟,以防止对象析构函数的这种显式调用。您想要显式调用析构函数的一个可能原因是,如果您想要覆盖删除操作符,或者您想要释放分配有新位置的对象。
  • @AlejandroDíaz 是的。 std::vector 之类的人经常这样做。 push_back et al 上的新位置,pop_back 上的显式销毁

标签: c++ destructor


【解决方案1】:

如果要显式调用析构函数,则必须从与被调用的析构函数相同类型的对象中调用它。否则,这将产生未定义的行为:

在显式析构函数调用中,析构函数名称显示为 ~ 后跟一个类型名或 decltypespecifier,表示 析构函数的类类型。析构函数的调用受制于 成员函数的通常规则(9.3);也就是说,如果对象是 不属于析构函数的类类型,也不属于从 析构函数的类类型(包括当析构函数通过 一个空指针值),程序具有未定义的行为。

还要注意,如果显式调用析构函数,则在实例所在作用域的末尾,如果对象已在堆栈上实例化,则析构函数将被隐式调用第二次。这也会产生未定义的行为:

一旦为一个对象调用析构函数,该对象就不再 存在;如果为某个对象调用析构函数,则行为未定义 生命周期已结束的对象 (3.8)。 [ 示例:如果析构函数 对于自动对象被显式调用,并且该块是 随后以通常会调用隐式的方式离开 对象的破坏,行为未定义

我建议您阅读规范的第 12.4 节。 working draft 是免费提供的。

【讨论】:

  • 第一个引号与您的注释不匹配。析构函数必须匹配对象,而不是指针。
【解决方案2】:

事实上,在某些情况下(即:当使用池来避免常量内存分配时),您通常会将析构函数作为普通函数调用,否则当您再次尝试使用placement new 时会导致未定义的行为那个内存地址。

T* Pool::alloc() {
    ...
    return new (memory) T();
}

void Pool::dealloc( T* memory ) {
    ...
    memory->~T();
}

关于非虚拟析构函数的问题,如果析构函数不是虚拟的,删除指向 A 的指针内的 B 对象会导致未定义的行为。

struct A
{
    ~A() { std::cout << "A destructor" << std::endl; }
};
struct B : A
{
    ~B() { std::cout << "B destructor" << std::endl; }
};

A* a = new B();
delete a; // undefined behaviour, just ~A() will be called

B* b = new B();
delete b; // it's OK because b is B*

更多信息in this FAQ

【讨论】:

    猜你喜欢
    • 2013-11-07
    • 2011-04-16
    • 1970-01-01
    • 2018-08-14
    • 2021-11-19
    • 2014-08-02
    • 1970-01-01
    • 2011-05-18
    • 1970-01-01
    相关资源
    最近更新 更多