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

C++ 与自定义分配器一起使用的最佳唯一指针是什么?,c++,pointers,c++11,unique-ptr,allocator,C++,Pointers,C++11,Unique Ptr,Allocator,我正在使用一个自定义分配器,它需要知道删除时对象的真实类型*它还应该取消分配与从分配函数接收到的地址完全相同的地址。这是我第一个使用任何特殊分配器的项目,所以我对最佳实践没有经验或知识。我希望本质上具有与唯一指针相同的语义。下面是显示问题的明显失败尝试: template<typename T> void* allocate(){ return malloc(sizeof(T)); //I really do something else but its beyond the

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

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需要调用my deallocate而不是delete

在这种情况下,最好使用什么指针

更新:

添加一个带类型擦除的有状态古装删除程序可能会解决我的问题。下面是一个std::函数的示例

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: 添加factory函数可以解决一些问题,并减少出错的可能性:

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
这是开始觉得有用,所以似乎我可能已经解决了我自己的问题。对于任何提出批评或想法的人,我仍然非常感激


*我原本不清楚这个要求

不确定这是否是您要找的。但是,通过使用函数指针作为具有唯一\u ptr的删除器,可以获得动态删除器的许多功能:


不确定这是否是你要找的。但是,通过使用函数指针作为具有唯一\u ptr的删除器,可以获得动态删除器的许多功能:


从您所展示的代码中,绝对没有理由知道派生类型:如果将基本析构函数设置为虚拟并调用它,它也会自动调用派生析构函数。对派生对象调用特定方法的任何其他操作都是设计错误,应该在派生类的析构函数中处理。@syam我知道需要虚拟析构函数,但是,在这种特定情况下,必须使用与allocate相同的类型调用我的deallocate函数。这需要一段时间来解释为什么会这样,让我们说这是一个要求。@当已经需要自定义分配器时,syam使客户机类多态通常不是一个可行的快速修复方法。从您所展示的代码中,绝对没有理由知道派生类型:只需将基本析构函数设置为虚拟并调用它,它也会自动调用派生析构函数。对派生对象调用特定方法的任何其他操作都是设计错误,应该在派生类的析构函数中处理。@syam我知道需要虚拟析构函数,但是,在这种特定情况下,必须使用与allocate相同的类型调用我的deallocate函数。我们需要一段时间来理解为什么会这样,我们就说这是一个要求。@当已经需要自定义分配器时,syam使客户端类多态通常不是一个可行的快速修复方法。是的,当然一个简单的函数指针比一个std::函数小得多,它满足了所有的要求。我想我说得太快了,由于vtable或多重继承的原因,当指向派生的指针和指向基址的指针不在同一地址时,这将立即中断,因为static_caststatic_castp并不总是等于p。看到了吗,这解决了问题,但灵活性较差,我想我会使用这种方法,但会添加一个工厂,这样用户就不会轻易破坏它。是的,当然,一个简单的函数指针比一个std::函数小得多,它满足了所有的要求。我想我说得太早了,由于vtable或多重继承的原因,当指向派生的指针和指向基址的指针不在同一地址时,这将立即中断,因为static_caststatic_castp并不总是等于p。看到这个解决了问题,但灵活性较差,我想我会使用这个方法,但会添加一个工厂,这样用户就不会轻易破坏它。
std::unique_ptr<Base, void(*)(void*)> p(allocate<Derived>(), deallocate<Derived>);
#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()