C++ 使用 shared_ptr 安全删除事件对象负载

C++ safely deleting event object payload with shared_ptr

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

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

我已经实施了解决方案 here,但是据我所知,这会导致在第一个事件处理程序之后(通过 unique_ptr)解除分配负载。

在下面的代码中,'setData' 尝试获取负载对象 (dataObject),并将其转换为 shared_ptr 以供 void* data 携带。 getData 执行 "reverse":

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;
}; 

您应该考虑 std::any instead of void*. That would avoid complex memory allocation for data. If you can't use C++17, it's not that hard to make your own implementation from 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 is used to perform 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<void>,并且 getData:

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