C++自定义删除
假设我们有以下代码:
class MyType
{
public: int x;
}
MyType* object = new MyType();
delete object;
是否可以在删除之前检查对象是否具有特定的 x 值?例如,如果 object.x 是奇数,则在调用 delete 后该对象不应从内存中释放。简而言之,为此类进行自定义删除,我们可以选择何时可以在操作员删除调用中释放对象。
回答
您试图解决的真正问题是什么?
首先,这个问答是对 C++ 中常见场景的一个很好的教训:
- 允许你做某事的语言,但仅仅因为你可以并不意味着你应该。
- 在尝试解决XY 问题时通常会遇到这些情况。
在这种特殊情况下,您可能会寻找完全不同的东西(X 问题),而不是尝试超载operator delete(解决 Y 问题,使 的客户感到困惑MyType),MyType如果和何时应删除(或不删除)它们,例如基于对象特征(例如数据成员的奇数)。有关最小示例,请参见例如@Ayxan Haqverdili 的回答。
从理论上解决错误识别的 Y 问题
对于这种用例,永远不要这样做。
您可以重载operator delete(以及operator delete[]),方法是将其定义为要重载的类的静态成员函数(按词法范围查找优先级)。正如评论中所指出的,虽然重载通常的释放函数是合法的,但尝试有条件地允许多次调用它不是:
struct MyType final {
int x;
static void operator delete(void *p) {
if (static_cast<MyType *>(p)->x % 2 == 1) {
::operator delete(p);
}
}
};
int main() {
MyType *object = new MyType{42};
delete object; // Not DEALLOCATED.
// Underlying object DESTRUCTED, however.
// Extremely confusing.
++(object->x);
delete object; // UB: destructor for underlying object called twice.
}
根据[expr.delete]/6 [强调我的]:
如果 delete-expression 的操作数的值不是空指针值并且所选的释放函数(见下文)不是销毁运算符 delete,则 delete-expression将调用对象或对象的析构函数(如果有)被删除的数组元素。
作为第二次尝试(强调:永远不要走这条路),我们可以通过重载和滥用(C++20 中的新)特定于类的销毁释放函数来避免调用析构函数(仅有条件地调用它) :
#include <iostream>
#include <new>
struct MyType final {
int x;
static void operator delete(MyType *p, std::destroying_delete_t) {
if (p->x % 2 == 1) {
p->~MyType();
::operator delete(p);
}
}
~MyType() {
std::cout << "ndtor";
}
};
int main() {
MyType *object = new MyType{42};
delete object; // Neither deallocated nor underlying object destructed.
++(object->x);
delete object; // Destructed and deallocated.
}
虽然这个程序格式良好,但它确实滥用了新的破坏性删除重载。Clang 确实会发出-Wmismatched-new-delete警告,这通常是对 UB 的提示,但这里可能不准确。
无论如何,这似乎是停止解决这个特定 Y 问题的徒劳之旅的好地方。
- @TedLyngmo 在对象生命周期之后(即在调用析构函数之后)访问对象是 UB。因此,GCC 可能会忽略 `++`。
- @TedLyngmo 析构函数被调用两次。在此处查看输出 https://gcc.godbolt.org/z/joT6odoW5
- @AyxanHaqverdili 谢谢,我会尽快删除(没有双关语)或更新答案。
- @dfrib 好节目!我不知道有这样的东西存在。强制性 [xkcd 参考](https://xkcd.com/356/)。