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

C++ safely deleting event object payload with shared_ptr

本文关键字:事件 删除 对象 有效 负载 安全 使用 shared ptr C++      更新时间:2023-10-16

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

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

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

在下面的代码中,'setData' 试图获取有效负载对象 (dataObject),并将其转换为由void* data携带的shared_ptrgetData做"相反":

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而不是void*。这将避免data的复杂内存分配。如果你不能使用 C++17,那么从 Kevlin Henney 的论文中制作自己的实现并不难(添加 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;
};
我在模板方法

的代码中使用了左值引用以避免重载:模板推导允许相同的方法接受命名变量以及具有或不具有恒常性的临时值。详情请看这里。

std::forward 用于执行完美转发。事实上,如果你从这样的左值构造一个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

完整演示


如果要继续使用指向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);
}