簡體   English   中英

使用帶有 std::unique_ptr 的抽象刪除器

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

我想要一個提供一些創建方法的運行時接口。 這些方法返回unique_ptr<T> ,我想通過創建 class 來啟用自定義刪除。 問題是我絕對不希望接口直接提供這些方法——它們應該只在破壞unique_ptr<T, SomeCustomDel> 現在,我想我可以使用std::unique_ptr<T, std::function<void(T*)>> ,但我真的不想這樣做,因為我根本不需要那種抽象級別而且我不需要不想支付堆分配。

有什么建議么?

您的規范對我來說並不完全清楚,但是您是否考慮過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::unique_ptr的標准“動態”刪除器版本。 這個神話般的 class 將允許我在實例化它時將刪除器附加到 unique_ptr ,類似於std::shared_ptr

也就是說,如果存在這樣的類型,我懷疑它基本上是用std::unique_ptr<T,std::function<void(T*)>>實現的。 你想避免的事情。

但是我認為您低估std::function 它的實現是優化以避免在可能的情況下碰到堆。 如果你的刪除器 object 仍然很小,那么一切都將在堆棧上完成(我認為boost::function可以靜態處理最大 32 字節的刪除器)。

A 用於刪除器過於籠統的問題。 您必須提供刪除器的定義。 沒有辦法解決這個問題。 但是,您不必讓用戶實例化 class,這實際上禁止他們使用它。 為此,刪除器的構造函數需要一個僅在實現文件中定義的標記結構。

或者可能是最簡單的解決方案。 將刪除器放在詳細命名空間中。 用戶仍然可以自由使用它,但很明顯,當您更改它時,他們不應該也不能抱怨,破壞了他們的代碼。

我看到兩個選項。

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

現在,您可以創建不同的upi<T>對象來存儲不同的 function 指針和刪除器狀態,而無需提及其類型中究竟發生了什么。 但這與實現“小型 function 優化”的function<>刪除器幾乎相同。 您可以期望一個體面的標准庫實現為不需要任何堆分配的小型 function 對象(如 function 指針)提供非常有效的function<>包裝器。 至少我會。 :)

選項 2:簡單地使用 shared_ptr 而不是 unique_ptr 並利用其內置的類型擦除功能來處理刪除器。 這也將允許您輕松支持 Derived->Base 轉換。 為了最大程度地控制分配的內容,您可以使用 std::allocate_shared function 模板。

這是對其中一個答案的回應,而不是對原始問題的回應。 僅僅因為格式原因,它是一個答案而不是評論。

我希望有std::unique_ptr的標准“動態”刪除器版本。 這個神話般的 class 將允許我在實例化它時將刪除器附加到unique_ptr ,類似於std::shared_ptr

這是實現此類 class 的開始。 這很容易做到。 我僅將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 盡管可以通過使用“小型刪除器”優化來緩解這種情況。 還有一個練習留給讀者。 :-)

我發現這個問題在谷歌上搜索我自己的問題; 使用帶有抽象基 class 指針的 unique_ptr。 所有的答案都很棒。 我發現@deft_code 最適合我的需求。

這就是我最終在我的情況下所做的:

假設 T 是一個抽象基 class 類型。 makeT(), func 創建一些派生的 class 的新實例,並返回 T*:

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

我只是想與那些正在尋找簡短、復制和粘貼解決方案的人分享這個。

對於未經訓練的 c++11 眼睛,[](... 語法是 lambda。

正如其他答案中提到的那樣,它不是 C 意義上的“函數指針”,而是可調用的 c++ object,它真的很小,應該有足夠的開銷來攜帶它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM