自定义堆分配器:智能指针内缓存的派生需求地址?

时间:2017-05-30 04:36:26

标签: c++ memory-management c++14

在具有许多自定义堆分配器的环境中,通常是否需要将原始void*的地址缓存在自定义智能指针中?

实施例

稍后识别某个内存块Allo的分配器(content): - 分配块时,我认为有必要在它附近存储一个hacky信息(meta-dataAllo*)。

enter image description here

当我想要取消分配void*时,我可以减去void*指针以找到Allo*,例如

template<class T>class StrongPointer{
    public: void* content;
    public: ~StrongPointer(){
        Allo* allo=static_cast<Allo*>((void*)(static_cast<char*>(content)-4));  
        //^ 32-bit system
        allo->deallocate(content);
    }
    public: void get(){ return static_cast<T*>(content); }
}

它应该有用。

然而,当我希望它支持施放StrongPointer<Derived> - &gt;时,这件事就会破裂。 StrongPointer<Base2>
(根据C++ virtual table layout of MI(multiple inheritance)

class Base1{/*some fields */};
class Base2{/*some fields */};
class Derived : public Base1,public Base2{};

例如,将StrongPointer<Derived>投射到StrongPointer<Base2>的结果将StrongPointer<Base2>::content不会直接 Allo*的位置。

enter image description here

template<class T1,class T2> StrongPointer<T2> cast(StrongPointer<T1>&& t1){
    StrongPointer<T2> r;
    r.content=static_cast<T2*>(t1.get());
    //^ location change, so "content-(4 bytes)" doesn't point to Allo* anymore
    return r;
} 

问题

在我看来,有一些解决方法: -

  • Allo*存储在每个强指针中。 OR
  • 存储偏移量+/-在每个铸件中
  • 商店Allo* + 1,反映已分配内容的真实地址

这一切归结为: - 我真的必须在Strong_Pointer<T>内存储另一个变量吗?

2 个答案:

答案 0 :(得分:3)

这里有一个人如何组织这个而不用侵入性而且不必使用像shared_ptr这样的胖指针(如果胖指针可以使用shared_ptr,我假设你想避免它们)。此示例不使用自定义智能指针,只需任何类型的指针即可。你唯一需要记住的是使用&#34; make&#34;只要你想使用自定义分配器就可以运行。

#include <cstdlib>
#include <cstddef>
#include <iostream>
#include <memory>
#include <cstring>

//元数据

struct Alloc;

struct UnalignedControlBlock
{
    int magic1;
    Alloc* allocator;
    std::size_t size;
    char magic2[19];
};

union ControlBlock
{
    UnalignedControlBlock ucb;
    std::max_align_t aligner;
};

//分配器

struct Alloc
{
    static Alloc global_allocator;

    static void* allocate(std::size_t size)
    {
        void* p = ::operator new (size + sizeof(ControlBlock));
        ControlBlock* cb = static_cast<ControlBlock*>(p);
        cb->ucb.allocator = &global_allocator;
        cb->ucb.magic1 = 42;
        cb->ucb.size = size;
        std::strcpy(cb->ucb.magic2, "Hey there!");
        std::cout << "Allocate: block: " << cb << " size: " << size << " magic1: " << cb->ucb.magic1 << " magic2: " << cb->ucb.magic2 << " allocator: " << cb->ucb.alloca
        return cb+1;
    }

    static void deallocate (void* p)
    {
        ControlBlock* cb = static_cast<ControlBlock*>(p);
        cb--;
        std::cout << "Deallocate: block: " << cb << " size: " << cb->ucb.size << " magic1: " << cb->ucb.magic1 << " magic2: " << cb->ucb.magic2 << " allocator: " << cb->u
        ::operator delete (cb);
    }
};

//自定义new和delete运算符的持有者(魔术发生的地方)

template <class T>
struct Allocated : T
{
    template <class ... Arg>
        Allocated(Arg ... arg) : T(arg ...) {}
    void* operator new (size_t size) { return Alloc::allocate(size); }
    void  operator delete (void* p) { return Alloc :: deallocate(p); }
};

// make函数(返回自己的智能指针)

template <class T, class ... Args>
std::unique_ptr<T> make_smart (Args ... args)
{
    return std::unique_ptr<T>(new Allocated<T>(args...));
};

//试驾

struct Test1
{
    const int filler = 42;
    virtual ~Test1() {
       std::cout << "Test1::~Test1 " << this << " " << filler << std::endl;
    }
};

struct Test2 : virtual Test1
{
    const int filler = 43;
    virtual ~Test2() {
       std::cout << "Test2::~Test2 " << this << " " << filler << std::endl;
    }
};

struct Test3 : virtual Test1
{
    const int filler = 44;
    virtual ~Test3() {
       std::cout << "Test3::~Test3 " << this << " " << filler << std::endl;
    }
};

struct Test4 : Test2, Test3
{
    const int filler = 45;
    virtual ~Test4() {
       std::cout << "Test4::~Test4 " << this << " " << filler << std::endl;
    }
};


Alloc Alloc::global_allocator;

int main ()
{
    std::unique_ptr<Test1> p1 = make_smart<Test1>();
    std::unique_ptr<Test1> p2 = make_smart<Test2>();
    std::unique_ptr<Test1> p3 = make_smart<Test3>();
    std::unique_ptr<Test1> p4 = make_smart<Test4>();
}

测试输出

Allocate: block: 0x1817c20 size: 16 magic1: 42 magic2: Hey there! allocator: 0x6052e9
Allocate: block: 0x1818080 size: 32 magic1: 42 magic2: Hey there! allocator: 0x6052e9
Allocate: block: 0x18180e0 size: 32 magic1: 42 magic2: Hey there! allocator: 0x6052e9
Allocate: block: 0x1818140 size: 48 magic1: 42 magic2: Hey there! allocator: 0x6052e9
Test4::~Test4 0x1818170 45
Test3::~Test3 0x1818180 44
Test2::~Test2 0x1818170 43
Test1::~Test1 0x1818190 42
Deallocate: block: 0x1818140 size: 48 magic1: 42 magic2: Hey there! allocator: 0x6052e9
Test3::~Test3 0x1818110 44
Test1::~Test1 0x1818120 42
Deallocate: block: 0x18180e0 size: 32 magic1: 42 magic2: Hey there! allocator: 0x6052e9
Test2::~Test2 0x18180b0 43
Test1::~Test1 0x18180c0 42
Deallocate: block: 0x1818080 size: 32 magic1: 42 magic2: Hey there! allocator: 0x6052e9
Test1::~Test1 0x1817c50 42
Deallocate: block: 0x1817c20 size: 16 magic1: 42 magic2: Hey there! allocator: 0x6052e9

这说明了从为继承调整的指针中正确恢复元数据。如果析构函数是虚拟的,则不需要自定义删除器。

答案 1 :(得分:2)

您应该使用标准shared_ptr。当您创建shared_ptr的新实例时,您可以定义将被调用以删除该内存的deleter functionor object)。
另一个简洁的特性是当你转换为shared_ptr<void>时保留了deleter类,因此shared_ptr机制也可以从void指针中正确处理删除。

您可以按类型设置模板化删除器,但这只是实现细微差别。

这里有一个删除器的例子(只是为了好玩)完全覆盖了删除并且根本没有删除对象。

#include <iostream>
#include <memory>

class Myclass
{
public:
    ~Myclass()
    {
        std::cout << "Myclass Destructor" << std::endl;
    }
};

struct MyDeleter {
    void operator()(Myclass* p) const {
        std::cout << "In custom destructor" << std::endl;
        //delete p;
    }
};


int main()
{
    {
        std::shared_ptr<Myclass> example1(new Myclass(), MyDeleter());
        std::cout << "Delete void example1 at end of scope, with no actual deletion" << std::endl;
    }
    {
        std::shared_ptr<Myclass> example2 = std::make_shared<Myclass>();
        std::cout << "Delete example2 at end of scope" << std::endl;
    }
    {
        std::shared_ptr<void> example3 = std::static_pointer_cast<void>(std::make_shared<Myclass>());
        std::cout << "Delete void example3 at end of scope" << std::endl;
    }

    return 0;
}

输出:

Delete void example1 at end of scope, with no actual deletion  
In custom destructor  
Delete example2 at end of scope  
Myclass Destructor  
Delete void example3 at end of scope  
Myclass Destructor  

因此,如果你坚持重新发明轮子,你可以看一下精心设计的轮子的shared_ptr实现。