与自定义分配器一起使用的最佳唯一指针是什么?

时间:2013-12-06 23:21:21

标签: c++ pointers c++11 unique-ptr allocator

我正在使用自定义分配器,它需要在删除时知道对象的真实类型。 *它还应该释放与从allocate函数接收的完全相同的地址。这是我的第一个使用任何特殊分配器的项目,所以我真的没有最佳实践的经验或知识。我想基本上具有与唯一指针相同的语义。这是一个明显失败的尝试,以显示问题:

template<typename T>
void* allocate(){
    return malloc(sizeof(T)); //I really do something else but its beyond the scope of this example
}

template<typename T>
void deallocate(T * p){
    // T must be real T and not base of T
    free(p); //I really do something else but its beyond the scope of this example
}
std::unique_ptr<Base> pb(new Base);
std::unique_ptr<Derived> pd(new Derived);
std::unique_ptr<Base> pb2(std::move(pd));
//std::unique_ptr<Derived> pd2(std::move(pb)); this should not compile
//pb.swap(pd); this should not compile
pb.swap(pb2);

这会失败,因为unique_ptr需要调用我的deallocate而不是删除。

在这种情况下使用的最佳指针是什么?

更新:

添加类型擦除的有状态服装删除器可能会解决我的问题。这是一个std :: function

的例子
template<typename T>
using UniquePtr = std::unique_ptr<T,std::function<void(T*)>>;

//using my special unique_ptr
UniquePtr<Base> upb(new (allocate<Derived>()) Derived,[](Base*p){
        if(p!=nullptr){
            auto d = static_cast<Derived*>(p);
            d->~Derived();
            deallocate(d); 
        }
    });

我想不出另一种让真实派生类的知识跟随指针的方法,在它被转换为基础然后移动的情况下。这看起来非常冗长且容易出错。这是最好的方式吗?

编辑2: 添加工厂功能可以解决一些丑陋问题,并且可以减少错误:

template<typename T_Base, typename T_Derived, typename... Ts>
std::unique_ptr<T_Base,std::function<void(T_Base* p)>> MakeUnique(Ts &&... Args){
    return std::unique_ptr<T_Base,std::function<void(T_Base*)>>(
        new (allocate<T_Derived>()) T_Derived(std::forward<Ts>(Args)...),
        [](T_Base*p){
            if(p!=nullptr){
                auto d = static_cast<T_Derived*>(p);
                d->~T_Derived();
                deallocate(d); 
            }
        });
}

//example use
auto mupd = MakeUnique<Base,Derived>();
auto mupdd = MakeUnique<Derived,Derived>();
auto mupb = MakeUnique<Base,Derived>();
auto mupbb = MakeUnique<Base,Base>();
mupbb.swap(mupb);
mupb = std::move(mupd);
//mupb = std::move(mupdd); should give error

这开始变得有用,所以看起来我可能已经解决了我自己的问题。对于提出批评或想法的人,我仍然非常感激。

*我原本不清楚这个要求。

1 个答案:

答案 0 :(得分:4)

不确定这是否是您要找的。但是,通过将函数指针用作unique_ptr的删除器,您可以获得“动态删除器”的许多功能:

std::unique_ptr<Base, void(*)(void*)> p(allocate<Derived>(), deallocate<Derived>);

删除器只是一个功能。但是在运行时,你可以为它提供指向模板化函数的指针,模板化为Derived类型。它允许调用正确的析构函数,即使析构函数不是虚拟的。虽然通过此指针调用的任何其他内容必须是虚拟的,否则它将解析为Base类。这是一个完整的HelloWorld:

#include <memory>
#include <new>
#include <cstdlib>

template<typename T>
T*
allocate()
{
    std::unique_ptr<T, void(*)(void*)> hold(static_cast<T*>(std::malloc(sizeof(T))),
                                                            std::free);
    ::new (hold.get()) T;
    return static_cast<T*>(hold.release());
}

template<typename T>
void
deallocate(void* p)
{
    static_cast<T*>(p)->~T();
    std::free(p);
}

#include <iostream>

struct Base
{
    Base() {std::cout << "Base()\n";}
    Base(const Base&) = delete;
    Base& operator=(const Base&) = delete;
    ~Base() {std::cout << "~Base()\n";}

    virtual void bark() const {std::cout << "Hi Base!\n";}
};

struct Derived
    : public Base
{
    Derived() {std::cout << "Derived()\n";}
    Derived(const Base&) = delete;
    Derived& operator=(const Derived&) = delete;
    ~Derived() {std::cout << "~Derived()\n";}

    void bark() const {std::cout << "Hi Derived!\n";}
};

int
main()
{
    std::unique_ptr<Derived, void(*)(void*)> p(allocate<Derived>(), deallocate<Derived>);
    p->bark();
    std::unique_ptr<Base, void(*)(void*)> p2 = std::move(p);
    p2->bark();
}

Base()
Derived()
Hi Derived!
Hi Derived!
~Derived()
~Base()