gpt4 book ai didi

c++ - 具有虚拟析构函数的池分配器

转载 作者:IT老高 更新时间:2023-10-28 22:22:25 29 4
gpt4 key购买 nike

我正在使用旧的 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 行引发以下警告:

warning: deleting ‘void*’ is undefined [-Wdelete-incomplete]

嗯,是的,很明显。但由于这些对象总是从池中分配的,我认为不需要自定义(非放置)operator delete。所以我尝试删除 D2 行。但这导致编译失败:

new.cc: In destructor ‘virtual B::~B()’: new.cc:9:8: error: no suitable ‘operator delete’ for ‘B’ struct B : A ^ new.cc: At global scope: new.cc:18:31: note: synthesized method ‘virtual B::~B()’ first required here B *doit1() { return new(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;,但这不会编译(“在 'struct A' 中没有匹配 'A::operator delete' 的成员” )。

相关但不同的问题:

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 不一定是正确的。

如果您认为您有充分的理由证明我对应该或不应该起作用的期望是错误的,我也想听听。

最佳答案

有趣的问题。如果我理解正确,您要做的就是根据它是否通过池分配来选择正确的删除运算符。

您可以在池中分配的 block 的开头存储一些额外的信息。

由于不能在没有池的情况下分配 B,因此您只需使用有关池的一些额外信息将其转发到普通 delete(void*) 运算符中的放置删除器。

Operator new 会将该部分存储在分配 block 的开头。

更新:感谢您的澄清。同样的技巧仍然适用于一些小的修改。更新了下面的代码。如果那仍然不是您想要做的,那么请提供一些正面和负面的测试用例来定义什么应该起作用,什么不应该起作用。

struct Pool
{
void* alloc(size_t s) {
// do the magic...
// e.g.
// return buf;
return buf;
}
void dealloc(void* m) {
// more magic ...
}
private:

char buf[256];
};
struct PoolDescriptor {
Pool* pool;
};


struct A
{
virtual ~A() { }
};

struct B : A
{
static void *operator new(std::size_t s){
auto desc = static_cast<PoolDescriptor*>(::operator new(sizeof(PoolDescriptor) + s));
desc->pool = nullptr;
return desc + 1;
}

static void *operator new(std::size_t s, Pool &p){
auto desc = static_cast<PoolDescriptor*>(p.alloc(sizeof(PoolDescriptor) + s));
desc->pool = &p;
return desc + 1;
}
static void operator delete(void *m, Pool &p) {
auto desc = static_cast<PoolDescriptor*>(m) - 1;
p.dealloc(desc);
}
static void operator delete(void *m) {
auto desc = static_cast<PoolDescriptor*>(m) - 1;
if (desc->pool != nullptr) {
throw std::bad_alloc();
}
else {
::operator delete (desc);
} // Line D2
}
};


Pool p;
void shouldFail() {
A* a = new(p)B;
delete a;
}
void shouldWork() {
A* a = new B;
delete a;
}

int main()
{
shouldWork();
shouldFail();
return 0;
}

关于c++ - 具有虚拟析构函数的池分配器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39336353/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com