使用带有std :: unique_ptr的抽象删除器

时间:2011-06-12 12:24:19

标签: c++ c++11 visual-studio-2010

我想拥有一个提供一些创建方法的运行时界面。这些方法返回unique_ptr<T>,我想通过创建类启用自定义删除。问题是我绝对不希望接口直接提供这些方法 - 它们只能在unique_ptr<T, SomeCustomDel>的销毁中使用。现在,我认为我可以使用std::unique_ptr<T, std::function<void(T*)>>,但我真的不愿意,因为我根本不需要这种抽象级别,我不想支付堆分配。

有什么建议吗?

5 个答案:

答案 0 :(得分:2)

您的说明书对我来说并不完全清楚,但您是否考虑过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();}
    ...
};

如果没有关于您的要求的更多详细信息,很难知道您的情况最好。

答案 1 :(得分:2)

我希望std::unique_ptr有一个标准的“动态”删除版本。这个神秘的类允许我在实例化时将unique_ptr附加到unique_ptr,类似于std::shared_ptr

如果说这种类型存在,我怀疑它基本上是用std::unique_ptr<T,std::function<void(T*)>>来实现的。你想避免的事情。

但是我认为你低估了std::function。它的实现是优化,以避免在可能的情况下击中堆。如果你的删除对象仍然很小,一切都将在堆栈上完成(我认为boost::function可以静态处理最大32字节的删除。)

对于过于笼统的删除问题。您必须提供删除器的定义。没有办法解决这个问题。但是,您不必让用户实例化该类,这实际上禁止他们使用它。为此,删除器的构造函数需要一个仅在实现文件中定义的标记结构。

或者可能是最简单的解决方案。将删除器放在详细命名空间中。用户仍然可以自由使用它,但很明显,当你更改它时,他们不应该也不会抱怨,打破他们的代码。

答案 2 :(得分:1)

我看到两个选项。

选项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-&gt; Base转换。为了最好地控制可以使用std :: allocate_shared函数模板分配的内容。

答案 3 :(得分:1)

这是对其中一个答案的回答,而不是原始问题。仅仅因为格式化原因,这是答案而不是评论。

  

我希望有一个标准的“动态”   删除版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。虽然可以通过使用“小删除器”优化来减轻这种情况。另一个练习留给读者。 : - )

答案 4 :(得分:0)

我发现这个问题在谷歌上搜索我自己的问题;将unique_ptr与抽象基类指针一起使用。所有答案都很棒。我发现@ deft_code是最符合我需求的。

这就是我最终在我的案例中做的事情:

假设T是抽象基类类型。 makeT(),func创建一些派生类的新实例,并返回T *:

std::unique_ptr<T, std::function<void(T*)>> p(makeT(), [](T* p){p.delete();});

我只想分享那些正在寻找简短,复制和粘贴解决方案的人。

对于未经训练的c ++ 11眼睛,[](...语法是lambda。

正如其他答案中所提到的,它不是C语义中的​​'函数指针',而是一个可调用的c ++对象,它实际上很小,并且应该具有可忽略的开销来承载它。