在显式销毁对象之后但在其内存被释放之前调用成员函数是否合法?

Is it legal to call member functions after an object has been explicitly destroyed but before its memory was deallocated?

本文关键字:调用 成员 函数 是否 释放 对象 之后 内存      更新时间:2023-10-16

我有这样的代码:

struct data {
  void doNothing() {}
};
int main() {
    data* ptr = new data();
    ptr->~data();
    ptr->doNothing();
    ::operator delete(ptr);
}

注意,doNothing()是在对象被销毁之后,但在其内存被释放之前被调用的。看起来"对象生命周期"已经结束,但是指针仍然指向正确分配的内存。成员函数不能访问任何成员变量。

在这种情况下成员函数调用是否合法?

是的,对于op中的代码来说。因为析构函数很简单,调用它并不会结束对象的生命周期。[basic.life]/p1:

类型为T的对象的生存期结束于:

  • 如果T是具有非平凡析构函数(12.4)的类类型,则开始析构函数调用,或
  • 对象占用的存储空间被重用或释放。

[class.dtor]/p5:

如果析构函数不是用户提供的,并且:

  • 析构函数不是virtual
  • 其类的所有直接基类都有简单的析构函数,并且
  • 对于类的所有非静态数据成员都是类类型(或其数组),每个这样的类都有一个简单的析构函数。

不,一般情况下不是这样。在对象生命周期结束后调用非静态成员函数是UB。[basic.life]/p5:

[A]在对象的生命周期结束之后,在对象的存储空间被占用之前被占用的对象被重用或释放,任何指向的指针对象将被或曾经被放置的存储位置可能是只在有限的情况下使用。对于正在构建或销毁的对象,请参见12.7。否则,这样的指针指的是已分配的存储(3.7.4.2),并像使用指针一样使用指针类型void*,定义良好。间接通过这样的指针就是允许,但产生的左值只能在有限的情况下使用,如下所述。程序没有定义行为如果:

  • […]
  • 指针用于访问非静态数据成员或调用对象的非静态成员函数,或者
  • […]

给[class.dtor]:

一旦为对象调用析构函数,对象将不再存在

这个片段来自[basic.life]:

…或者,在之后,对象的生命周期已经结束,并且在对象占用的存储空间之前重用或释放,任何指向对象将要或曾经所在的存储位置的指针可以使用,但只在有限的情况下使用……如果:
程序有未定义的行为
-…指针用于访问非静态数据成员或调用类的非静态成员函数对象

规定你拥有的是未定义的行为。然而,这里有不同的语言——"对象不再存在"answers"对象已经结束",在[basic]中。生活],据说:

初始化完成。T 类型对象的生命周期在结束时:
-如果T是具有非平凡析构函数的类类型(12.4),则开始析构函数调用,或
-对象占用的存储空间被重用或释放。

一方面,你没有一个重要的析构函数,所以[基本的]。表示对象的生命期还没有结束——存储还没有被重用或释放。另一方面,阶级。dr]暗示对象"不再存在",这听起来当然应该是"结束"的同义词,但事实并非如此。

我想"语言律师"的答案是:从技术上讲,这不是未定义的行为,看起来完全合法。"代码质量"的答案是:不要这样做,它充其量是令人困惑的。

其他答案都是正确的,但遗漏了一个细节:

如果析构函数或构造函数是平凡的,则允许。其他答案已经清楚地解释了,如果析构函数是微不足道的,则原始对象的生命周期还没有结束。

但是如果构造函数是平凡的,那么只要存在适当大小和对齐方式的内存位置,对象就存在。因此,即使使用非平凡的析构函数和平凡的构造函数,也存在一个可以调用成员的全新对象。

其他答案省略的废话,紧接在他们引用的生命周期结束规则之前,说

对象的生存期是对象的运行时属性。如果对象是类或聚合类型,并且它或它的一个成员是由普通默认构造函数以外的构造函数初始化的,则该对象被称为具有非空初始化。[注:通过简单的复制/移动构造函数进行初始化是非空初始化。]T类型对象的生命周期从以下时刻开始:

    对于T类型,
  • 存储具有正确的对齐方式和大小,并且
  • 如果对象进行了非空初始化,则表示初始化完成。

关于在旧对象的存储中创建新对象的重要注意事项:由于构造简单,没有对数据成员执行初始化,并且它们现在都具有不确定的值,因此必须在读取任何值之前设置它们的值(通过初始化或调用不使用先前值的赋值操作符)。

在OP的情况下,原始对象仍然存在,因此此警告不适用。