c++ - 在指向类类型对象的指针上使用 delete-expression 的合法性,其生命周期已结束的平凡析构函数/标量类型

标签 c++ language-lawyer

下面的代码合法吗?

struct S
{};
int main()
{
    S* p = new S;
    p->~S();
    delete p;
}

标准规则位于 [basic.life#6] :

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated24 or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see [class.cdtor]. Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.allocation]), and using the pointer as if the pointer were of type void* is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below. The program has undefined behavior if:

(6.1) — the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression,

(6.2) — the pointer is used to access a non-static data member or call a non-static member function of the object

[basic.life#6.1] 不适用,所以这段代码似乎是合法的,因为 S 有一个普通的析构函数。

然而,根据[basic.life#6.2],这段代码是非法的,因为delete-expression调用了S对象的析构函数,这是它的一个非静态成员函数。

编辑:根据[class.dtor#19] ,也是违法的。

我应该听什么?可能 [basic.life#6.2],因为 [basic.life#6.1] 没有规定对于具有平凡析构函数的类这样做必须是合法的。

结论是不合法。

但是,将 S 替换为标量类型后,情况似乎有所不同:

#include <memory>
int main()
{
    char* p = new char;
    std::destroy_at(p);
    delete p;
}

根据[expr.call#5],这将对p 指向的char 对象进行伪析构函数调用,这将结束其生命周期。 :

If the postfix-expression names a pseudo-destructor (in which case the postfix-expression is a possibly-parenthesized class member access), the function call destroys the object of scalar type denoted by the object expression of the class member access ([expr.ref], [basic.life]).

[basic.life#6.1] 仍然不适用。

但不同的是 [basic.life#6.2] 也不适用,因为没有为 char 对象调用非静态成员函数 (char 毕竟不是一个类)。

结论是合法的。

为什么?

最佳答案

好像是措辞有问题。您的代码要么是合法的,要么不是。如果它是合法的,那么 [basic.life]/6.2 应该有一个普通析构函数的异常(exception)。如果它不是合法的,那么应该从 [basic.life]/6.1 中删除“使用非平凡的析构函数”这句话,因为即使析构函数是平凡的,不可避免的析构函数调用也会违反 p6.2。

我怀疑它是合法的,因为在 delete 的情况下保留显式异常是没有意义的,如果不是为了防止破坏实际上调用 的代码对具有超出其生命周期的普通析构函数的对象删除

N2762删除了所有其他导致 UB 的指针操作的普通析构函数异常,这些操作指向超出其生命周期的对象的指针,但明显保留它用于 delete-expression。这表明需要在语言中保留此异常以避免破坏代码。

最有可能的原因是为什么第二个项目符号没有也被更改为一个普通的析构函数调用的异常(exception)是作者忘记了一个delete-expression确实调用析构函数,即使它是微不足道的。他们可能假设 delete 被指定为跳过微不足道的析构函数调用。

(另请注意,在 C++98 到 C++17 中,S 对象的生命周期不会因析构函数调用而结束,因为 [basic.life]/1 指定析构函数调用仅当对象不平凡时才结束对象的生命周期;这在 C++20 中由 CWG2256 更改。但是,由于 N2762,在 C++11 及更高版本中,如果对象的生命周期尚未 started 因为它具有重要的初始化,我们可能会遇到与 OP 描述的问题类似的问题。)

我认为就此提交一份缺陷报告是值得的,尽管我不确定结果会是什么。自 N2762 以来已经过去了这么多时间,我想委员会实际上会考虑此时是否应该消除平凡析构函数的异常(,修复 p6.1 而不是 p6.2 );毕竟,CWG2256 的决议也可能破坏了一些代码,但自从 C++ 从 C 中分离出来以来,这种代码可能已经变得越来越少了。

关于c++ - 在指向类类型对象的指针上使用 delete-expression 的合法性,其生命周期已结束的平凡析构函数/标量类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73434558/

相关文章:

c++ - std::transform with lambda: 跳过一些项目

c++ - #define/#undef 是具有特殊含义的标识符的未定义行为吗?

c++ - 所有派生类的通用接口(interface)

c++ - 如何避免将参数传递给基于引用的帮助程序类的模板实例化

c++ - T D[N] 是否总是声明数组类型的对象?

c - 在声明 `int foo() {}` 之后定义 `int foo(void) {}` 与 `int foo(void);`

c++ - 在计算 constexpr 时抛出异常会发生什么?

c++ - 在 C/C++ 中使用 `typedef` 的暗角?

c++ - Koenig Lookup 的奇怪行为

C++ 相关名称 : Is this typename required?