重载运算符delete以不删除对象-有效或未定义的行为?
前言:我正在优化一个旧的代码库。
我有一个命名的类Token,我为令牌的子集(但不是全部)添加了缓存。缓存的令牌可能不会被删除,因为它们的指针在程序的整个生命周期内都存储在内存中的永久集合中。
不幸的是,代码库delete token到处都是。所以我所做的是添加一个bool cached从内部检查的成员Token::operator delete和析构~Token()函数,如果cached为真,它会立即从这些各自的函数返回。
我的问题是,这是未定义的行为,还是我可以这样做?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在不自由的内存operator delete,但它是UB已删除的对象内访问任何东西。delete token将首先调用 的析构函数token,然后它会调用operator delete. 即使你自己在析构函数体内什么都不做,一旦函数体返回,它会继续销毁该对象的所有子对象,一旦整个析构函数返回,整个对象就被认为不再存在。即使您的数据成员是像bools 或void*s这样的标量,在销毁时通常不会触及这些实现,该语言也会认为它们无法被析构函数访问。在您的示例中,operator delete无法访问cached,然后,如果您仍然有一个指向周围已销毁对象的“缓存”指针,则使用该指针也是 UB。这就是为什么您收到 avoid*而不是 a Token*in 的operator delete原因,operator delete也是隐式static(no this) 的原因:它表示您的对象已经消失了。
如果你在 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 */ }
};
当这样的重载operator delete存在时,delete token将调用重载而不调用析构函数本身,因此您可以安全地选择简单地不销毁对象。当然,现在您负责自己调用析构函数。
如果你不能做到这一点,你就有点不走运了。您可能会尝试void Token::operator delete(void*) = delete;找到deleteon Tokens 的所有用途,以便您可以替换它们。最好你Token*用某种智能指针(无论std::shared_ptr是你自己写的还是什么)替换你的(我假设)s ,这样你就不再需要有那么多的deletes了。