c - 是一个对象已经被显式销毁,但是之前其内存被释放后, 调用成员功能是申明?

  显示原文与译文双语对照的内容

我有以下代码:


struct data {
 void doNothing() {}
};

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

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

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

时间:

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

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

  • 如果 T 是具有non-trivial析构函数( 12.4 )的类类型,则析构函数调用开始,或者
  • 对象占用的存储被重用或者释放。

[class.dtor]/p5:

析构函数不是user-provided并且如果:

  • 析构函数不是 virtual
  • 它的类的所有直接基类都有一些琐碎的析构函数,
  • 所有non-static类的类类型的数据成员( 或者数组), 每一个这样的类都有一个微不足道的析构函数。

不,不在一般情况下。 在对象的生存期结束后调用non-static成员函数已经结束。 [basic.life]/p5:

[A] fter对象的生命周期已经结束,或者在对象占用的存储之前被释放或者释放。任何指向存储位置的指针都可以使用,但只能使用有限的路径。 对于正在构造或者销毁的对象,请参见 12.7. 否则,此类指针引用已经分配的存储( 3.7.4.2 ),并使用指针作为指针,就像指针是 void* 一样。 通过这样一个指针的间接寻址被允许,但结果的左值只能用有限的方式使用,如下所述。 程序有未定义的行为如果:

  • [... ]
  • 指针用于访问non-static数据成员或者调用该对象的non-static成员函数,或者
  • [... ]

给定 [class.dtor]:

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

来自 [basic.life]的Fragment:

之后,或者一个对象的生命周期已经结束之前重用对象占用的存储或释放,任何指针,指的是存储位置对象将或位于可以使用,但只有在有限的方式。 程序有未定义的行为如果:
— 。。
—指针用于访问non-static数据成员或者调用对象的non-static成员函数

规定你拥有的是未定义的行为。 但是,这里有不同的语言-"对象不再存在"与"对象已经结束",以及 [basic.life] 中的早期,它指出:

其初始化完成。一个类型的对象的生命周期 T 结束时间:
—如果 T 是具有non-trivial析构函数( 12.4 )的类类型,则会启动析构函数调用,或者
—对象占用的存储被重用或者释放。

一方面,你没有non-trivial析构函数,因此 [basic.life] 表明对象的生命周期还没有结束- 存储没有被重用或者释放。 另一方面,[class.dtor] 建议对象"不再存在",它应该是"结束"的同义词,但不是。

我想"language-lawyer"答案是: 它在技术上并不是未定义的行为,似乎完全合法。 "代码质量"回答是: 不要这么做,最好是 confusing 。

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

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

但是如果构造函数是微不足道的,那么只要存在适当大小和对齐的内存位置,就会存在一个对象。 所以即使有了non-trivial析构函数和简单构造函数,也有一个新的对象可以调用。

其他答案排除的废话,立即先于end-of-lifetime规则引用,说

对象的生存期是对象的运行时属性。 如果一个对象是类或者聚合类型,或者它或者它的一个成员是由一个非平凡默认构造函数初始化的构造函数,那么一个对象被称为 non-vacuous初始化 。 [ 注意:由一个简单的复制/移动构造函数初始化是non-vacuous初始化。 —结束注释] 类型的对象的生存期在以下时间开始:

  • 获取类型 T的适当对齐和大小的存储,并且
  • 如果对象有non-vacuous初始化,则初始化完成。
...