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

C++ 具有虚拟析构函数的池分配器,c++,language-lawyer,C++,Language Lawyer,我正在使用一个旧的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(

我正在使用一个旧的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; }
Pool p;
B *b = new (p) B;
...
b->~B();
// worry about the pool later
但这感觉不对。。。我的意思是,我是谁坚持你必须从游泳池中分配这些东西

另一个显而易见的解决方案(以及我目前正在使用的):

但这也感觉不对,因为我如何知道我调用的是正确的删除函数

我想,我真正想要的是
使用::操作符delete,但不会编译(“没有与“结构A”中的“A::operator delete”匹配的成员”)

相关但不同的问题:

[更新,扩展一点]

我忘了提到
A
的析构函数在我们当前的应用程序中实际上不需要是
虚拟的。但是,从具有非虚拟析构函数的类派生时,当您提高警告级别时,会导致一些编译器抱怨,而本练习的初衷是消除此类警告

另外,为了明确所需的行为。。。正常用例如下所示:

#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; }
Pool p;
B *b = new (p) B;
...
b->~B();
// worry about the pool later
也就是说,就像大多数使用placement new一样,您可以直接调用析构函数。或者调用一个helper函数来为您执行此操作

我不希望下面的方法奏效;事实上,我认为这是一个错误:

Pool p;
A *b_upcast = new (p) B;
delete b_upcast;
检测并失败这种错误的使用是可以的,但前提是这样做不会给非错误的情况增加任何开销。(我怀疑这是不可能的。)

最后,我确实希望这能奏效:

A *b_upcast = new B;
delete b_upcast;
换句话说,我希望支持但不要求对这些对象使用池分配器

我目前的解决方案基本上是有效的,但我担心直接调用
::operator delete
不一定是正确的


如果你认为你有一个很好的论点,认为我对什么应该或不应该起作用的期望是错误的,那么我也想听听这个问题。

有趣的问题。如果我理解正确,您要做的是根据是否通过池分配来选择正确的delete操作符

您可以在池中分配的块的开头存储一些关于此的额外信息

由于B不能在没有池的情况下被分配,所以您只需使用关于池的一点额外信息转发给普通删除(void*)操作符中的placement deleter

新操作员将在分配块的开头存储该部件

更新: 谢谢你的澄清。同样的技巧在一些小的修改后仍然有效。更新代码如下。 如果这仍然不是你想要做的,那么请提供一些积极和消极的测试用例来定义什么应该和什么不应该工作

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;
}
结构池 { 空*所有(尺寸){ //做魔术。。。 //例如。 //返回buf; 返回buf; } 无效解除锁定(无效*m){ //更神奇。。。 } 私人: char-buf[256]; }; 结构池描述符{ 游泳池*游泳池; }; 结构A { 虚拟~A(){} }; 结构B:A { 静态void*运算符新(标准::大小\u t s){ auto desc=static_cast(::运算符new(sizeof(PoolDescriptor)+s)); desc->pool=nullptr; 返回desc+1; } 静态无效*运算符新(标准::大小、池和p){ auto desc=静态_cast(p.alloc(sizeof(PoolDescriptor)+s)); 描述->池=&p; 返回desc+1; } 静态void操作符删除(void*m、Pool&p){ auto desc=静态(m)-1; p、 dealoc(desc); } 静态void操作符删除(void*m){ auto desc=静态(m)-1; 如果(描述->池!=nullptr){ 抛出std::bad_alloc(); } 否则{ ::操作员删除(描述); }//第D2行 } }; p池; void shouldFail(){ A*A=新(p)B; 删除一条; } void shouldWork(){ A*A=新的B; 删除一条; } int main() { 应该工作(); 应该失败(); 返回0; }
很难理解这段代码将要实现什么,因为您已经去掉了其中的重要部分

您是否知道只有当B的构造函数抛出异常时才会调用
静态void操作符delete(void*m,Pool&p){}

15) 如果已定义,则由自定义单个对象放置调用“新建” 如果对象的构造函数 抛出异常。如果定义了特定于类的版本(25),则 优先于(9)调用。如果未提供(25)或(15) 用户不调用释放函数

这意味着在当前示例中,永远不会调用此运算符delete(D1)

对我来说,有一个带有虚拟析构函数的基类a,并且坚持认为delete调用的语义是不同的,这取决于对象的创建方式,这看起来很奇怪

如果您确实需要基类A,并且添加了虚拟析构函数只是为了使警告静音,那么您可以使析构函数在A中受保护,而不是使其虚拟。像这样-

struct A
{
protected:
  ~A() { }
};

struct B final : public A
{
  ~B() = default;

  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) {} // Line D2

};

delete操作符与析构函数是分开的。delete操作符必须释放内存,然后调用析构函数。你有虚拟析构函数--你试过虚拟删除操作符吗?我想知道这是否可能。您可以尝试一个返回“对象是如何分配的”的虚拟函数,并使用该函数来决定如何在基本运算符delete中释放。如果有人从池中创建
B
,但随后通过父
a
指针将其删除,您希望发生什么情况?@Johnnycras:我知道区别(和交互)在delete操作符和析构函数之间,我尽量小心我的措辞。删除运算符始终是“静态”的,即使您没有这样声明它们。(尽管它们的行为有点像虚拟的,这是我问题的根源。)您可以使用
-c