问题
I need to create an Event
object to be dispatched by an event listener system. The Event
needs to have following properties:
Event
can potentially be handled by 0..n listener objects.Event
contains a void pointer which can point to an arbitrary object (payload) (unknown type at build time). Event listener will convert to appropriate type depending onEvent
's name.- Need the payload object to be (automatically) deleted once the event has been dispatched to interested parties. Original event raiser cannot deallocate as event goes into an asvnc queue.
- Assume listeners can make shallow copy of payload when event is processed.
I have implemented the solution here, but AFAIK this causes the payload to be deallocated (via the unique_ptr
) after the first event handler.
In the code below, 'setData' attempts to take the payload object (dataObject
), and to convert it into a shared_ptr
to be carried by void* data
. getData
does the "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;
};
回答1:
You should consider 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 (add parts missing from C++17 specification, e.g. move constructor).
Your code may become something like that:
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;
};
I used in my code lvalue references in the template methods to avoid overloading: template deduction allows the same method to accept named variables as well as temporary values, with or without constness. See here for details.
std::forward is used to perform perfect forwarding. Indeed, if you construct an Event
from an lvalue like this:
Event e{"Hello", Foo{}};
Calling setData
without perfect forwarding will pass dataObject
as an lvalue since it is a named variable in this context:
setData(dataObject); // will call Foo's copy constructor
Perfect forwarding will pass dataObject
as an rvalue, but only if it was constructed from an rvalue in the first place:
setData(std::forward<T>(dataObject)); // will call Foo's move constructor
If dataObject
had been constructed from an lvalue, the same std::forward
will pass it as an lvalue, and yield copy constructor invokation, as wanted:
Foo f{};
Event e{"Hello", f};
// ...
setData(std::forward<T>(dataObject)); // will call Foo's copy constructor
Complete demo
If you want to keep using pointers to void
, you can embed an appriopriate deleter with the 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);
}
);
}
With data
being declared as shared_ptr<void>
, and getData
:
template<class T> T getData() const
{
using data_t = typename std::decay<T>::type;
return *std::static_pointer_cast<data_t>(data);
}
来源:https://stackoverflow.com/questions/40843719/c-safely-deleting-event-object-payload-with-shared-ptr