C++ safely deleting event object payload with shared_ptr

≯℡__Kan透↙ 提交于 2019-12-11 04:26:34

问题


I need to create an Event object to be dispatched by an event listener system. The Event needs to have following properties:

  1. Event can potentially be handled by 0..n listener objects.
  2. 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 on Event's name.
  3. 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.
  4. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!