C++ 使用 shared_ptr 安全删除事件对象负载
C++ safely deleting event object payload with shared_ptr
我需要创建一个 Event
对象以由事件侦听器系统调度。 Event
需要具有以下属性:
Event
可能由 0..n 个侦听器对象处理。
Event
包含一个空指针,它可以指向任意对象(有效负载)(构建时未知类型)。事件侦听器将根据 Event
的名称转换为适当的类型。
- 需要在事件发送给相关方后(自动)删除有效负载对象。当事件进入 asvnc 队列时,原始事件引发器无法解除分配。
- 假设侦听器可以在处理事件时对负载进行浅表复制。
我已经实施了解决方案 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
如果您想继续使用指向 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);
}
我需要创建一个 Event
对象以由事件侦听器系统调度。 Event
需要具有以下属性:
Event
可能由 0..n 个侦听器对象处理。Event
包含一个空指针,它可以指向任意对象(有效负载)(构建时未知类型)。事件侦听器将根据Event
的名称转换为适当的类型。- 需要在事件发送给相关方后(自动)删除有效负载对象。当事件进入 asvnc 队列时,原始事件引发器无法解除分配。
- 假设侦听器可以在处理事件时对负载进行浅表复制。
我已经实施了解决方案 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
如果您想继续使用指向 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);
}