【问题标题】:C++ safely deleting event object payload with shared_ptrC++ 使用 shared_ptr 安全删除事件对象有效负载
【发布时间】:2016-11-28 11:56:09
【问题描述】:

我需要创建一个Event 对象以由事件侦听器系统分派。 Event 需要具有以下属性:

  1. Event 可能由 0..n 个侦听器对象处理。
  2. Event 包含一个 void 指针,它可以指向任意对象(有效负载)(构建时的未知类型)。事件侦听器将根据Event 的名称转换为适当的类型。
  3. 一旦将事件分派给相关方,需要(自动)删除有效负载对象。当事件进入 asvnc 队列时,原始事件引发程序无法解除分配。
  4. 假设侦听器可以在处理事件时制作负载的浅拷贝。

我已经实现了解决方案 here,但是 AFAIK 这会导致在第一个事件处理程序之后释放有效负载(通过 unique_ptr)。

在下面的代码中,'setData' 尝试获取有效负载对象 (dataObject),并将其转换为 shared_ptr 以供 void* data 携带。 getData 做“反转”:

class Event {

public:
std::string name;

Event(std::string n = "Unknown", void* d = nullptr) :name(n), data(d) {}

template<class T>  void setData(const T dataObject)
{
    //Create a new object to store the data, pointed to by smart pointer
    std::shared_ptr<T> object_ptr(new T);
    //clone the data into the new object
    *object_ptr = dataObject;

    //data will point to the shared_pointer
    data= new std::shared_ptr<T>(object_ptr);
}


//reverse of setData.
template<class T>  T getData() const
{
    std::unique_ptr<
        std::shared_ptr<T>
        > data_ptr((std::shared_ptr<T>*) data);
    std::shared_ptr<T> object_ptr = *data_ptr;

    return *object_ptr;
}

private:
    void* data;
}; 

【问题讨论】:

    标签: c++ c++11 events smart-pointers


    【解决方案1】:

    您应该考虑std::any 而不是void*。这将避免为data 分配复杂的内存。如果您不能使用 C++17,那么从 Kevlin Henney's paper 制作自己的实现并不难(添加 C++17 规范中缺少的部分,例如移动构造函数)。

    你的代码可能会变成这样:

    class Event {
    
    public:
    std::string name;
    
    Event() :name("Unknown") {}
    
    template<class T>
    Event(std::string n, T&& dataObject) :name(n)
    {
        setData(std::forward<T>(dataObject));
    }
    
    template<class T>  void setData(T&& dataObject)
    {
        using data_t = typename std::decay<T>::type;
        data = std::make_shared<data_t>(std::forward<T>(dataObject));
    }
    
    //reverse of setData.
    template<class T>  T getData() const
    {
        using data_t = typename std::decay<T>::type;
        return *any_cast<std::shared<data_t>>(data);
    }
    
    private:
        any data;
    };
    

    我在我的代码中使用了模板方法中的左值引用来避免重载:模板推导允许相同的方法接受命名变量以及临时值,无论是否具有常量。详情请见here

    std::forward 用于执行perfect forwarding。事实上,如果你从这样的左值构造一个Event

    Event e{"Hello", Foo{}};
    

    在没有完美转发的情况下调用setData 会将dataObject 作为左值传递,因为它在此上下文中是一个命名变量:

    setData(dataObject); // will call Foo's copy constructor
    

    完美转发会将dataObject 作为右值传递,但仅当它首先是从右值构造的:

    setData(std::forward<T>(dataObject)); // will call Foo's move constructor
    

    如果dataObject 是从左值构造的,则相同的std::forward 会将其作为左值传递,并根据需要产生复制构造函数调用:

    Foo f{};
    Event e{"Hello", f};
    
    // ...
    
    setData(std::forward<T>(dataObject)); // will call Foo's copy constructor
    

    Complete demo


    如果您想继续使用指向void 的指针,可以使用shared_ptr 嵌入适当的删除器:

    template<class T>  void setData(T&& dataObject)
    {
        using data_t = typename std::decay<T>::type;
        data = std::shared_ptr<void>(
            new data_t{std::forward<T>(dataObject)},
            [](void* ptr)
            {
                delete static_cast<data_t*>(ptr);
            }
        );
    }
    

    data 被声明为shared_ptr&lt;void&gt;getData

    template<class T>  T getData() const
    {
        using data_t = typename std::decay<T>::type;
        return *std::static_pointer_cast<data_t>(data);
    }
    

    【讨论】:

    • 哇!这里有很多我不知道的类型和指针语言特性,需要一两天来消化..
    • 请随时寻求解释,以便我可以用更多细节更新我的答案。这将有助于未来的读者;)
    • 首先:为什么在ctor中调用forward(),如果我们只是在ctor中调用setData(dataObject);会发生什么?
    • 让我明白这一点; T&& 和 forward 构造都是关于有效地处理右值,但核心功能不需要。衰减 是允许数组/fn_pointers,但在最小的概念证明中又不需要?只是问一下,这样我就可以把它归结为要点。
    • 这个结构没有实名,这是std::shared_ptr 的一个特性。见constructor's documentation (4-7)。请注意,删除器是一个完全可访问的属性:请参阅get_deleter
    猜你喜欢
    • 2018-07-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-26
    • 2012-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-05
    相关资源
    最近更新 更多