C++ 重载运算符delete不删除对象-有效或未定义的行为?
前言:我正在优化一个旧的代码库 我有一个名为C++ 重载运算符delete不删除对象-有效或未定义的行为?,c++,C++,前言:我正在优化一个旧的代码库 我有一个名为Token的类,我为其中的一部分(但不是全部)添加了缓存。缓存的令牌不能被删除,因为它们的指针在程序的整个生命周期内都存储在内存中的永久集合中 不幸的是,代码库到处都有delete令牌。因此,我所做的是添加一个boolcached成员,该成员从Token::operator delete和析构函数~Token()内部进行检查,如果cached为true,它将立即从这些相应的函数返回 我的问题是,这是未定义的行为,还是我可以这样做?对未被删除的内容执行d
Token
的类,我为其中的一部分(但不是全部)添加了缓存。缓存的令牌不能被删除,因为它们的指针在程序的整个生命周期内都存储在内存中的永久集合中
不幸的是,代码库到处都有delete令牌
。因此,我所做的是添加一个boolcached
成员,该成员从Token::operator delete
和析构函数~Token()
内部进行检查,如果cached
为true,它将立即从这些相应的函数返回
我的问题是,这是未定义的行为,还是我可以这样做?对未被删除的内容执行delete
操作符可以吗?或者这会在将来咬我吗
class Token
{
bool cached;
void* data;
public:
~Token()
{
if (this->cached) return;
free(data);
}
void operator delete(void* p)
{
if (((Token*)p)->cached) return;
::operator delete(p);
}
// operator new, constructor etc.
}
这不好。不释放操作员删除中的内存不是UB,而是访问已删除对象中的任何内容是UBdelete token
将首先调用token
的析构函数,然后调用operator delete
。即使您自己在析构函数的主体内什么也不做,一旦主体返回,它将继续销毁对象的所有子对象,并且一旦整个析构函数返回,整个对象将被视为不再存在。即使您的数据成员是标量,如bool
s或void*
s(销毁时实现通常不会触及),该语言也会认为析构函数无法访问它们。在您的示例中,operator delete
无法访问cached
,如果您仍然有一个指向周围已销毁对象的“cached”指针,则使用该指针也是不可取的。这就是为什么在operator delete
中接收到void*
而不是Token*
,也就是为什么operator delete
隐式地static
(无此
):它表示您的对象已经消失
如果您使用的是C++20语言,则可以使用销毁运算符delete:
struct Token {
// ...
bool cached;
void operator delete(Token *thiz, std::destroying_delete_t) {
if(thiz->cached) return;
thiz->~Token();
::operator delete(thiz);
}
~Token() { /* clean up without worrying about cached */ }
};
当运算符delete
的重载存在时,delete token
将调用重载而不调用析构函数本身,因此您可以安全地选择不销毁对象。当然,现在你要自己负责调用析构函数
如果你做不到这一点,你就有点不走运了。您可以尝试void Token::operator delete(void*)=delete
查找令牌
上的删除
的所有用法,以便您可以替换它们。最好用某种智能指针(无论是std::shared\u ptr
还是您自己编写的东西)替换您的(我猜)令牌*
s,这样你就不再需要有那么多的delete
s了。添加一个你的Token
类的最小示例…检查cached
内部的~Token()
是没有用的,因为到那时对象已经在被销毁的过程中了。我添加了一个示例来说明问题。尽管我现在突然想到,如果类包含有自己的析构函数的对象,它们会被销毁,对此我无能为力(这里不是这样,但仍然如此)@RemyLebeau如果我没有弄错的话,在运算符delete之前调用析构函数?因此,真正的目标不是防止销毁令牌
对象,而是销毁它们内部包含的数据
?这与你最初提出的问题完全不同。如果是这样的话,您可能根本不需要重载操作符。这将有助于展示代币的实际使用情况;当cached为true时,我不希望销毁令牌或数据。如果operator delete配置为不释放令牌背后的底层内存,则不会释放令牌。如果析构函数在释放它之前返回,数据也不会被释放。回答很好,谢谢。我觉得创建一个名为Delete的方法是值得的,它在执行操作符Delete
之前检查缓存,然后执行搜索和替换。C++20选项看起来不错,如果没有缺点的话(除了必须手动调用析构函数)接下来的问题:如果operator new
中的void*
为Token::operator new
中的void*
为?(前提是构造器根本不改变成员)@korri123是的,这也很糟糕<在构造对象之前调用code>操作符new
;非销毁运算符delete
在销毁后被调用。由于对象不存在,两者都无法访问该对象的任何成员。这是一个有原因的void*
。