我正在使用自定义分配器,它需要在删除时知道对象的真实类型。 *它还应该释放与从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
这开始变得有用,所以看起来我可能已经解决了我自己的问题。对于提出批评或想法的人,我仍然非常感激。
*我原本不清楚这个要求。
答案 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()