unique_ptr<T>
,我想让创建类启用自定义删除。问题是,我绝对不想让接口直接提供这些方法-它们只应该在 unique_ptr<T, SomeCustomDel>
的销毁中可用。现在,我想到可以使用std::unique_ptr<T, std::function<void(T*)>>
,但我真的不想这样做,因为我根本不需要那种抽象层次,也不想支付堆分配的代价。有什么建议吗?
unique_ptr<T>
,我想让创建类启用自定义删除。问题是,我绝对不想让接口直接提供这些方法-它们只应该在 unique_ptr<T, SomeCustomDel>
的销毁中可用。现在,我想到可以使用std::unique_ptr<T, std::function<void(T*)>>
,但我真的不想这样做,因为我根本不需要那种抽象层次,也不想支付堆分配的代价。我希望有一个标准的"动态"删除器版本的std::unique_ptr
。这个神秘的类会允许我在实例化时附加一个删除器到unique_ptr,就像std::shared_ptr
一样。
话虽如此,如果这样的类型存在,我认为它基本上是用std::unique_ptr<T,std::function<void(T*)>>
实现的。这正是你想要避免的事情。
然而,我认为你低估了std::function
。它的实现是优化,以避免尽可能地击中堆栈。如果您的删除器对象保持较小,则所有操作都将在堆栈上完成(我认为boost::function
可以静态处理大小不超过32字节的删除器)。
对于一个太普遍的删除器问题。您必须提供删除器的定义。没有其他方法。但是,您不必让用户实例化该类,这基本上禁止他们使用它。为此,请使删除器的构造函数需要一个标记结构,在实现文件中仅定义该结构。
或者可能最简单的解决方案。将删除器放入详细信息命名空间中。用户仍然可以自由使用它,但很明显他们不应该并且不能抱怨您更改它时破坏了他们的代码。
unique_ptr<T, void(*)(void*)>
?这是一种非常灵活的类型,具有许多动态删除器的特性。class impl
{
public:
virtual ~impl();
virtual void operator()(void*) = 0;
virtual void other_functionality() = 0;
};
class my_deleter
{
impl* p_;
public:
...
void operator()(void* p) {(*p_)(p);}
void other_functionality() {p_->other_functionality();}
...
};
如果没有更多关于您需求的详细信息,很难确定什么最适合您。
std::function<void(void*)>
作为删除器有任何改进。 - sellibitzevoid*
,而且 impl
可以直接指向分配类。由于 unique_ptr
是在另一侧创建的,所以我不需要在主要分配类中公开这个接口,这正是我想要的。 - Puppy我看到两个选项。
选项1: 使用包含函数指针和必要时可编码一些状态的原始 char 数组的自定义删除器:
template<class T>
void simply_delete(T* ptr, const unsigned char*) {
delete ptr;
}
template<class T, int StateSize>
struct my_deleter {
void (*funptr)(T*,const unsigned char*);
array<unsigned char,StateSize> state;
my_deleter() : funptr(&simply_delete<T>) {}
void operator()(T* ptr) const {
funptr(ptr,StateSize>0 ? &state[0] : nullptr);
}
};
template<class T>
using upi = unique_ptr<T,my_deleter<T,sizeof(void*)>>;
upi<T>
对象,存储不同的函数指针和删除器状态,而无需在其类型中提及确切发生了什么。但这几乎与实现“小型函数优化”的function<>
删除器相同。您可以期望一个体面的标准库实现为小型函数对象(如函数指针)提供非常高效的function<>
包装器,而不需要任何堆分配。至少我是这样认为的。:)
选项2:只需使用shared_ptr而不是unique_ptr,并利用其内置的类型抹除功能来处理删除器。这也将使您能够轻松支持Derived->Base转换。为了最大程度地控制分配的位置,您可以使用std::allocate_shared函数模板。
这是对其中一个答案的回复,而不是原始问题的回复。由于格式原因,它是一个答案而不是评论。
我希望有一个标准的“动态”删除器版本的
std::unique_ptr
。这个神话般的类将允许我在实例化时附加一个删除器到unique_ptr
,类似于std::shared_ptr
。
这里是这样一个类的实现的开始。这相当容易做到。我只使用unique_ptr
作为异常安全辅助,没有更多的用途。它不像你想象的那样功能齐全。这些额外的功能留给读者自己练习。 :-) 下面的内容建立了指针的唯一所有权和自定义动态删除器的存储。请注意,即使智能指针的构造函数抛出异常(这实际上是unique_ptr
在实现中最有用的地方),智能指针仍然拥有传入的指针。
#include <memory>
#include <type_traits>
namespace detail
{
class impl
{
public:
virtual ~impl() {};
};
template <class T, class D>
class erase_type
: public impl
{
T* t_;
D d_;
public:
explicit erase_type(T* t)
noexcept(std::is_nothrow_default_constructible<D>::value)
: t_(t)
{}
erase_type(T* t, const D& d)
noexcept(std::is_nothrow_copy_constructible<D>::value)
: t_(t),
d_(d)
{}
erase_type(T* t, D&& d)
noexcept(std::is_nothrow_move_constructible<D>::value)
: t_(t),
d_(std::move(d))
{}
virtual ~erase_type()
{
if (t_)
d_(t_);
}
erase_type(const erase_type&) = delete;
erase_type& operator=(const erase_type&) = delete;
};
} // detail
template <class T>
class my_pointer
{
T* ptr_;
detail::impl* impl_;
public:
my_pointer() noexcept
: ptr_(nullptr),
impl_(nullptr)
{}
template <class Y>
explicit my_pointer(Y* p)
: ptr_(static_cast<T*>(p)),
impl_(nullptr)
{
std::unique_ptr<Y> hold(p);
impl_ = new detail::erase_type<Y, std::default_delete<Y>>(p);
hold.release();
}
template <class Y, class D>
explicit my_pointer(Y* p, D&& d)
: ptr_(static_cast<T*>(p)),
impl_(nullptr)
{
std::unique_ptr<Y, D&> hold(p, d);
typedef
detail::erase_type<Y, typename std::remove_reference<D>::type>
ErasedType;
impl_ = new ErasedType(p, std::forward<D>(d));
hold.release();
}
~my_pointer()
{
delete impl_;
}
my_pointer(my_pointer&& p) noexcept
: ptr_(p.ptr_),
impl_(p.impl_)
{
p.ptr_ = nullptr;
p.impl_ = nullptr;
}
my_pointer& operator=(my_pointer&& p) noexcept
{
delete impl_;
ptr_ = p.ptr_;
impl_ = p.impl_;
p.ptr_ = nullptr;
p.impl_ = nullptr;
return *this;
}
typename std::add_lvalue_reference<T>::type
operator*() const noexcept
{return *ptr_;}
T* operator->() const noexcept
{return ptr_;}
};
unique_ptr
(并像shared_ptr
一样)不同,接受指针的构造函数不是noexcept
。虽然可以通过使用“小删除器”优化来缓解这种情况。这是留给读者的另一个练习。 :-)我在谷歌上搜索自己的问题时发现了这个问题;使用unique_ptr与抽象基类指针。所有答案都很好。我发现@deft_code的回答最适合我的需求。
这是我在我的情况下最终采取的做法:
假设T是一个抽象基类类型。makeT()函数创建某个派生类的新实例,并返回T*:
std::unique_ptr<T, std::function<void(T*)>> p(makeT(), [](T* p){p.delete();});
我只是想为那些正在寻找简短、复制和粘贴式解决方案的人分享这个。
对于未经训练的C++11开发者来说,[](...语法是一个lambda表达式。
正如其他答案中提到的那样,它不是C语言中的“函数指针”,而是一个可调用的C++对象,非常小巧,携带它的开销应该可以忽略不计。
unique_ptr
的自定义删除器版本到底哪里出了问题?你确定它比标准删除器版本产生更多开销吗?MSVC 中shared_ptr
的实现是 Advanced STL episode 中第一个讨论的话题,其中两个版本的shared_ptr
类在内部复杂性方面非常相似。如果返回unique_ptr<T>
是最清晰的解决方案,也许您不需要太过担心它的代价。 - Kerrek SBunique_ptr
的开销,而在于std::function
的额外开销。 - Puppy