简体   繁体   English

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

[英]Using an abstract deleter with std::unique_ptr

I want to have a run-time interface that offers some creation methods.我想要一个提供一些创建方法的运行时接口。 These methods return unique_ptr<T> , and I want to enable custom deletion by the creating class.这些方法返回unique_ptr<T> ,我想通过创建 class 来启用自定义删除。 The thing is that I definitely don't want the interface to offer these methods directly- they should only be available in the destruction of the unique_ptr<T, SomeCustomDel> .问题是我绝对不希望接口直接提供这些方法——它们应该只在破坏unique_ptr<T, SomeCustomDel> Now, I figured that I can use std::unique_ptr<T, std::function<void(T*)>> , but I'd really rather not because I simply don't need that level of abstraction and I don't want to have to pay the heap allocation.现在,我想我可以使用std::unique_ptr<T, std::function<void(T*)>> ,但我真的不想这样做,因为我根本不需要那种抽象级别而且我不需要不想支付堆分配。

Any suggestions?有什么建议么?

Your specification isn't completely clear to me, but have you considered unique_ptr<T, void(*)(void*)> ?您的规范对我来说并不完全清楚,但是您是否考虑过unique_ptr<T, void(*)(void*)> This is a very flexible type with many qualities of a dynamic deleter.这是一种非常灵活的类型,具有动态删除器的许多特性。

If that isn't what you're looking for, you might try something along the lines of:如果这不是您想要的,您可以尝试以下方式:

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();}
    ...
};

It is difficult to know what is best in your case without more details about your requirements.如果没有关于您的要求的更多详细信息,很难知道什么是最适合您的情况。

I wish there was a standard "dynamic" deleter version of std::unique_ptr .我希望有std::unique_ptr的标准“动态”删除器版本。 This mythical class would allow me to attach a deleter to the unique_ptr when I instantiate it, similar to std::shared_ptr .这个神话般的 class 将允许我在实例化它时将删除器附加到 unique_ptr ,类似于std::shared_ptr

That said if such a type existed I suspect it would essentially be implemented with std::unique_ptr<T,std::function<void(T*)>> .也就是说,如果存在这样的类型,我怀疑它基本上是用std::unique_ptr<T,std::function<void(T*)>>实现的。 The very thing you wanted to avoid.你想避免的事情。

However I think you're underestimating std::function .但是我认为您低估std::function Its implementation is optimization to avoid hitting the heap if possible.它的实现是优化以避免在可能的情况下碰到堆。 If your deleter object remains small everything will be done on the stack (I think boost::function can statically handle deleters up to 32 bytes in size).如果你的删除器 object 仍然很小,那么一切都将在堆栈上完成(我认为boost::function可以静态处理最大 32 字节的删除器)。

A for the problem of a too general deleter. A 用于删除器过于笼统的问题。 You have to provide the definition of the deleter.您必须提供删除器的定义。 There is no way around that.没有办法解决这个问题。 However you don't have to let the user instantiate the class, which essentially forbids them from using it.但是,您不必让用户实例化 class,这实际上禁止他们使用它。 To do this make the deleter's constructor(s) require a tag structure that is only defined in the implementation file.为此,删除器的构造函数需要一个仅在实现文件中定义的标记结构。

Or possibly the simplest solution.或者可能是最简单的解决方案。 Put the deleter in a detail namespace.将删除器放在详细命名空间中。 The user is still free to use it, but it's obvious that they should not and can't complain when you change it, breaking their code.用户仍然可以自由使用它,但很明显,当您更改它时,他们不应该也不能抱怨,破坏了他们的代码。

I see two options.我看到两个选项。

Option 1: Use a custom deleter that contains a function pointer and optionally a raw char array to encode some state if necessary:选项 1:如果需要,使用包含 function 指针和可选的原始字符数组的自定义删除器对一些 state 进行编码:

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*)>>;

Now, you can create different upi<T> objects that store different function pointers and deleter states without the need of mentioning what exactly is happening in its type.现在,您可以创建不同的upi<T>对象来存储不同的 function 指针和删除器状态,而无需提及其类型中究竟发生了什么。 But this is almost the same as a function<> deleter which implements the "small function optimization".但这与实现“小型 function 优化”的function<>删除器几乎相同。 You can expect a decent standard library implementation to provide a very efficient function<> wrapper for small function objects (like function pointers) that don't require any heap allocations.您可以期望一个体面的标准库实现为不需要任何堆分配的小型 function 对象(如 function 指针)提供非常有效的function<>包装器。 At least I do.至少我会。 :) :)

Option 2: Simply use shared_ptr instead of unique_ptr and make use of its built-in type erasure feature with respect to deleters.选项 2:简单地使用 shared_ptr 而不是 unique_ptr 并利用其内置的类型擦除功能来处理删除器。 This will also allow you to support Derived->Base conversions easily.这也将允许您轻松支持 Derived->Base 转换。 For greatest control over what is allocated where you could use the std::allocate_shared function template.为了最大程度地控制分配的内容,您可以使用 std::allocate_shared function 模板。

This is a response to one of the answers, not to the original question.这是对其中一个答案的回应,而不是对原始问题的回应。 It is an answer instead of a comment simply because of formatting reasons.仅仅因为格式原因,它是一个答案而不是评论。

I wish there was a standard " dynamic " deleter version of std::unique_ptr .我希望有std::unique_ptr的标准“动态”删除器版本。 This mythical class would allow me to attach a deleter to the unique_ptr when I instantiate it, similar to std::shared_ptr .这个神话般的 class 将允许我在实例化它时将删除器附加到unique_ptr ,类似于std::shared_ptr

Here is a start of an implementation of such a class.这是实现此类 class 的开始。 It is fairly easy to do.这很容易做到。 I've used unique_ptr only as an exception safety aid, nothing more.我仅将unique_ptr用作异常安全辅助,仅此而已。 It is not as full-featured as you might like.它并不像您希望的那样功能齐全。 These extra features are left as an exercise for the reader.这些额外的功能留给读者作为练习。 :-) What is below establishes unique ownership of the pointer and storage for the custom dynamic deleter. :-) 下面的内容为自定义动态删除器建立了指针和存储的唯一所有权。 Note that the smart pointer owns a passed-in pointer even if the constructor of the smart pointer throws (this is actually where unique_ptr is most useful in the implementation).请注意,即使智能指针的构造函数抛出异常,智能指针也拥有传入的指针(这实际上是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_;}
};

Note that unlike unique_ptr (and like shared_ptr ), the constructors taking a pointer are not noexcept .请注意,与unique_ptr (和shared_ptr一样)不同,采用指针的构造函数不是noexcept Although that could possibly be mitigated via the use of a "small deleter" optimization.尽管可以通过使用“小型删除器”优化来缓解这种情况。 Yet another exercise left for the reader.还有一个练习留给读者。 :-) :-)

I found this question googling my own problem;我发现这个问题在谷歌上搜索我自己的问题; using unique_ptr with an abstract base class pointer.使用带有抽象基 class 指针的 unique_ptr。 All the answers are great.所有的答案都很棒。 I found @deft_code's one to be the best for my needs.我发现@deft_code 最适合我的需求。

This is what I ended up doing in my case:这就是我最终在我的情况下所做的:

Suppose T is an abstract base class type.假设 T 是一个抽象基 class 类型。 The makeT(), func creates a new instance of some derived class, and returns the T*: makeT(), func 创建一些派生的 class 的新实例,并返回 T*:

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

I just wanted to share this for those who are looking for short, copy and pasteish solution.我只是想与那些正在寻找简短、复制和粘贴解决方案的人分享这个。

For the untrained c++11 eyes, the [](... syntax is a lambda.对于未经训练的 c++11 眼睛,[](... 语法是 lambda。

As is mentioned in the other answers, it's not a 'function pointer' in the C sense, but a callable c++ object, which is really tiny, and should have negligible overhead to carry it around.正如其他答案中提到的那样,它不是 C 意义上的“函数指针”,而是可调用的 c++ object,它真的很小,应该有足够的开销来携带它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM