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

C++ 在对象被显式销毁后但在其内存被释放之前调用成员函数是否合法?,c++,destructor,language-lawyer,object-lifetime,explicit-destructor-call,C++,Destructor,Language Lawyer,Object Lifetime,Explicit Destructor Call,我有以下代码: struct data { void doNothing() {} }; int main() { data* ptr = new data(); ptr->~data(); ptr->doNothing(); ::operator delete(ptr); } 请注意,doNothing()是在对象被销毁后但其内存被释放之前调用的。看起来“对象生存期”已经结束,但是指针仍然指向正确分配的内存。成员函数不访问任何成员变量 在这种

我有以下代码:

struct data {
  void doNothing() {}
};

int main() {
    data* ptr = new data();
    ptr->~data();
    ptr->doNothing();
    ::operator delete(ptr);
}
请注意,
doNothing()
是在对象被销毁后但其内存被释放之前调用的。看起来“对象生存期”已经结束,但是指针仍然指向正确分配的内存。成员函数不访问任何成员变量


在这种情况下,成员函数调用合法吗?

是的,在OP中的代码中。因为析构函数很简单,调用它不会结束对象的生命周期。[基本生活]/p1:

类型为
T
的对象的生存期在以下情况下结束:

  • 如果
    T
    是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者
  • 对象占用的存储被重用或释放
[class.dtor]/p5:

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

  • 析构函数不是虚拟的
  • 它的类的所有直接基类都有平凡的析构函数,并且
  • 对于其类中属于类类型(或其数组)的所有非静态数据成员,每个此类类都有一个简单的 析构函数
不,一般情况下不会。在对象的生存期结束后调用非静态成员函数是一种错误。[基本生活]/p5:

[A] 在对象的生存期结束之后和存储之前 被占用的对象被重用或释放,任何引用 对象将位于或曾经位于的存储位置可能是 仅以有限的方式使用。对于正在建造或破坏的物体,见12.7。否则,这样的指针将引用已分配的 存储(3.7.4.2),并将指针当作 类型
void*
,定义良好。通过这样一个指针进行间接寻址是非常困难的 允许,但由此产生的左值只能以有限的方式使用, 如下所述。程序未定义 以下情况下的行为:

  • [……]
  • 指针用于访问非静态数据成员或调用对象的非静态成员函数,或
  • [……]
给定[class.dtor]:

为对象调用析构函数后,对象不再存在

这段来自[基本生活]的片段:

。。。或者,在对象的生命周期结束之后,在对象占用的存储空间被删除之前 重复使用或释放,指对象将位于或曾经位于的存储位置的任何指针 可以使用,但只能以有限的方式使用。。。如果:
-…
-指针用于访问非静态数据成员或调用 反对

规定您拥有的是未定义的行为。然而,这里有不同的语言——“对象不再存在”和“对象已经结束”,在[basic.life]的前面,它说:

其初始化已完成。 类型为
T
的对象的生存期在以下时间结束:
-如果
T
是具有非平凡析构函数(12.4)的类类型,则析构函数调用将启动,或者
-对象占用的存储被重用或释放

一方面,您没有一个非平凡的析构函数,因此[basic.life]表明对象的生存期尚未结束-存储尚未被重用或释放。另一方面,[class.dtor]表明对象“不再存在”,这听起来肯定像是“结束”的同义词,但实际上并非如此


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

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

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

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

其他答案遗漏的措辞,紧接着在他们引用的生命终结规则之前,说

对象的生存期是对象的运行时属性。如果一个对象属于类或聚合类型,并且它或它的一个成员是由一个构造函数(而不是一个普通的默认构造函数)初始化的,则称该对象具有非真空初始化。[注:由普通复制/移动构造函数进行的初始化是非真空初始化。-结束注]类型为
T
的对象的生存期始于:

  • 获得类型
    T
    的正确对齐和尺寸的存储,以及
  • 如果对象具有非真空初始化,则其初始化已完成
关于如何使用在旧已销毁对象的存储中创建的新对象的一个重要注意事项:由于构造简单,没有对数据成员执行初始化,它们现在都具有不确定的值,因此必须设置它们的值(通过初始化或调用不使用前一个值的赋值运算符)在读取任何值之前


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

无论答案如何,这都会让以后试图维护代码的人感到非常困惑。不要这样做,即使它以某种方式被证明是合法的(或在某个场景中工作)我认为这是不合法的,但是我读C++规范已经有一段时间了,我可能是错的。