【发布时间】:2017-01-13 03:56:48
【问题描述】:
我正在使用旧的 C++03 代码库。一个部分看起来像这样:
#include <cstddef>
struct Pool
{ char buf[256]; };
struct A
{ virtual ~A() { } };
struct B : A
{
static void *operator new(std::size_t s, Pool &p) { return &p.buf[0]; }
static void operator delete(void *m, Pool &p) { } // Line D1
static void operator delete(void *m) { delete m; } // Line D2
};
Pool p;
B *doit() { return new(p) B; }
也就是说,B 派生自 A,但 B 的实例是从内存池中分配的。
(注意这个例子有点过于简单了......实际上,池分配器做了一些不平凡的事情,因此需要在 D1 行放置 operator delete。)
最近,我们在更多编译器上启用了更多警告,第 D2 行引发以下警告:
警告:删除‘void*’未定义[-Wdelete-incomplete]
嗯,是的,很明显。但由于这些对象总是从池中分配的,我认为不需要自定义(非放置)operator delete。所以我尝试删除 D2 行。但这导致编译失败:
new.cc:在析构函数“virtual B::~B()”中:new.cc:9:8: error: no 适合“B”结构 B 的“运算符删除”:A ^ new.cc:在全局范围内:new.cc:18:31:注意:这里首先需要合成方法‘virtual B::~B()’ B *doit1() { return 新的(p)B; }
一点研究确定问题出在 B 的虚拟析构函数上。它需要调用非放置B::operator delete,因为某个地方的某个人可能会尝试通过A * 来delete 和B。由于名称隐藏,第 D1 行呈现默认的非放置 operator delete 不可访问。
我的问题是:处理此问题的最佳方法是什么?一个明显的解决方案:
static void operator delete(void *m) { std::terminate(); } // Line D2
但这感觉不对……我的意思是,我是谁坚持让你必须从池中分配这些东西?
另一个明显的解决方案(以及我目前使用的):
static void operator delete(void *m) { ::operator delete(m); } // Line D2
但这也感觉不对,因为我怎么知道我调用了正确的删除函数?
我想,我真正想要的是using A::operator delete;,但这无法编译(“没有匹配‘A::operator delete’的成员在‘struct A’中”)。
相关但不同的问题:
Why is delete operator required for virtual destructors
Clang complains "cannot override a deleted function" while no function is deleted
[更新,扩大一点]
我忘了提到A 的析构函数在我们当前的应用程序中并不需要是virtual。但是从具有非虚拟析构函数的类派生会导致一些编译器在您提高警告级别时抱怨,而练习的最初目的是消除此类警告。
另外,为了明确期望的行为......正常的用例如下所示:
Pool p;
B *b = new (p) B;
...
b->~B();
// worry about the pool later
也就是说,就像大多数使用placement new 一样,您可以直接调用析构函数。或者调用一个辅助函数来为你做这件事。
我不会期望以下工作;事实上,我认为这是一个错误:
Pool p;
A *b_upcast = new (p) B;
delete b_upcast;
检测到这种错误使用并失败是可以的,但前提是它可以在不对非错误情况增加任何开销的情况下完成。 (我怀疑这是不可能的。)
最后,我确实希望这会起作用:
A *b_upcast = new B;
delete b_upcast;
换句话说,我想支持但不要求为这些对象使用池分配器。
我目前的解决方案大部分都有效,但我担心直接调用 ::operator delete 不一定是正确的。
如果您认为您有充分的理由证明我对应该或不应该起作用的期望是错误的,我也想听听。
【问题讨论】:
-
删除操作符与析构函数是分开的。删除操作符必须释放内存然后调用析构函数。你有虚拟析构函数——你尝试过虚拟删除操作符吗?我想知道这是否可能。您可以尝试一个返回“对象是如何分配的”的虚函数,并使用它来决定如何在基本运算符 delete 中释放。
-
如果有人从池中创建了一个
B,然后被父A指针删除,你想发生什么? -
@johnnycrash:我知道删除运算符和析构函数之间的区别(和交互),并且我在措辞上尽量小心。删除运算符始终是“静态的”,即使您没有这样声明它们。 (虽然它们的行为有点像虚拟的,这是我问题的根源。)如果你想测试你的想法,你可以使用
-c或-S自己编译我的示例。 -
@MarkB:这是个好问题。我可以想到不止一个合理的答案...为了争论起见,假设在这种情况下的答案是:那将是错误的用法,但
A *a = new B; delete a;不会。让我们进一步说,检测这种错误使用是可取的,但前提是它可以在不对非错误情况造成任何开销的情况下完成。 -
Pool p;被销毁时会有未定义的行为,因为它的存储被重新使用了
标签: c++ language-lawyer