[英]Overload operator delete to not delete object - valid or undefined behavior?
Preface: I'm optimizing an old codebase.前言:我正在优化一个旧的代码库。
I have a class named Token
for which I added caching to for a subset of the tokens (but not all).我有一个名为
Token
的 class ,我为其添加了缓存以用于令牌的子集(但不是全部)。 Cached tokens may not be deleted as their pointers are stored in a permanent collection in memory for the whole lifetime of the program.缓存的令牌可能不会被删除,因为它们的指针在程序的整个生命周期内都存储在 memory 的永久集合中。
Unfortunately, the codebase has delete token
all over the place.不幸的是,代码库到处都有
delete token
。 So what I did was add a bool cached
member that is checked from inside Token::operator delete
and destructor ~Token()
, which returns from these respective functions immediately if cached
is true.所以我所做的是添加一个
bool cached
成员,该成员从Token::operator delete
和析构函数~Token()
内部检查,如果cached
为真,它会立即从这些各自的函数返回。
My question is, is this undefined behavior, or is it okay for me to do?我的问题是,这是未定义的行为,还是我可以这样做? Is it okay to execute the
delete
operator on something that doesn't get deleted?可以对未删除的内容执行
delete
运算符吗? Or will this bite me in the future?或者这会在未来咬我吗?
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.
}
This is not OK.这不行。 It is not UB to not free memory in
operator delete
, but it is UB to access anything inside the deleted object.在
operator delete
中不释放 memory不是UB,但访问已删除 object 中的任何内容是UB。 delete token
will first call the destructor for token
, and then it will call operator delete
. delete token
将首先调用token
的析构函数,然后调用operator delete
。 Even if you yourself do nothing inside the body of the destructor, once the body returns it will continue destroy all subobjects of the object, and once the whole destructor returns the whole object is considered to no longer exist.即使你自己在析构函数的主体内什么也不做,一旦主体返回它会继续销毁 object 的所有子对象,一旦整个析构函数返回整个 object 就被认为不再存在。 Even if your data members are scalars like
bool
s or void*
s, which implementations don't usually touch when destroying, the language considers them to be made inaccessible by the destructor.即使您的数据成员是像
bool
s 或void*
s 之类的标量,在销毁时通常不会触及这些实现,该语言也认为它们无法被析构函数访问。 In your example, operator delete
cannot access cached
, and later, if you still have a "cached" pointer to the destroyed object around, using that pointer is also UB.在您的示例中,
operator delete
无法访问cached
,稍后,如果您仍然有一个指向已破坏 object 的“缓存”指针,则使用该指针也是 UB。 This is why you receive a void*
and not a Token*
in operator delete
, and also why operator delete
is implicitly static
(no this
): it signifies that your object is already gone.这就是您在
operator delete
中收到void*
而不是Token*
的原因,也是operator delete
隐含为static
(没有this
)的原因:它表示您的 object 已经消失了。
If you're in C++20 land, you can use destroying operator delete instead:如果您在 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 */ }
};
When such an overload of operator delete
exists, delete token
will call the overload without calling the destructor itself, so you can safely choose to simply not destroy the object.当存在这样的
operator delete
重载时, delete token
将调用重载而不调用析构函数本身,因此您可以安全地选择不销毁 object。 Of course, now you're responsible for calling the destructor yourself.当然,现在你有责任自己调用析构函数。
If you can't do that, you're somewhat out of luck.如果你不能做到这一点,那你就有点不走运了。 You might try
void Token::operator delete(void*) = delete;
您可以尝试
void Token::operator delete(void*) = delete;
to find all uses of delete
on Token
s so you can replace them.在
Token
上查找delete
的所有用途,以便您可以替换它们。 Preferably you'd replace your (I presume) Token*
s with some kind of smart pointer (whether std::shared_ptr
or something you write yourself), so that you no longer need to have as many delete
s at all.最好你用某种智能指针(无论是
std::shared_ptr
还是你自己写的东西)替换你的(我认为) Token*
,这样你就不再需要有尽可能多的delete
了。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.