2016-11-28 2 views
2

Мне нужно создать объект Event, который будет отправлен системой прослушивания событий. Event должен иметь следующие свойства:C++ безопасно удаляет полезную нагрузку объекта события с shared_ptr

  1. Event потенциально могут быть обработаны 0..n объектов слушателя.
  2. Event содержит указатель на пустоту, который может указывать на произвольный объект (полезная нагрузка) (неизвестный тип во время сборки). Слушатель событий преобразуется в соответствующий тип в зависимости от Event.
  3. Нужно, чтобы объект полезной нагрузки был (автоматически) удален после того, как событие было отправлено заинтересованным сторонам. Исходный сборщик событий не может освободиться, поскольку событие переходит в очередь asvnc.
  4. Предположим, что слушатели могут сделать мелкую копию полезной нагрузки при обработке события.

Я реализовал решение here, но AFAIK это приводит к полезной нагрузке должны быть высвобождено (через unique_ptr) после первого обработчика события.

В приведенном ниже коде, «SetData» пытается принять объект полезной нагрузки (dataObject) и преобразовать его в shared_ptr, чтобы нести void* data. getData делает «обратный»:

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

ответ

2

Вы должны рассмотреть std::any вместо void*. Это позволит избежать сложного распределения памяти для data. Если вы не можете использовать C++ 17, не так сложно сделать свою собственную реализацию от Kevlin Henney's paper (добавьте части, отсутствующие в спецификации C++ 17, например, move constructor).

Ваш код может стать чем-то вроде этого:

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

я использовал в моих ссылках Lvalue коды в методах шаблона, чтобы избежать перегрузок: шаблон вычетом позволяет тот же метод, чтобы принять имена переменных, а также временные значения с константой или без нее. См. here.

std::forward использовано для выполнения perfect forwarding.Действительно, если построить Event из Lvalue, как это:

Event e{"Hello", Foo{}}; 

Calling setData без совершенной пересылки будет проходить dataObject как Lvalue, так как это именованная переменная в этом контексте:

setData(dataObject); // will call Foo's copy constructor 

Совершенная экспедиторская будет проходить dataObject как rvalue, но только если он был построен из rvalue на первом месте:

setData(std::forward<T>(dataObject)); // will call Foo's move constructor 

Если dataObject был построен из Lvalue, то же std::forward будет передать его в качестве Lvalue, и выход конструктор копирования invokation, так как хотел:

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); 
} 
+1

Wow !, Есть ряд типов и языка указателей особенности здесь, что я не знал, потребуется день или два, чтобы переварить .. – Ken

+1

Не стесняйтесь просить объяснений, поэтому я могу уточнить свой ответ с более подробной информацией. Это поможет будущим читателям;) – wasthishelpful

+0

Прежде всего: почему 'forward()' вызов в ctor, что произойдет, если мы просто назовем setData (dataObject); 'в ctor? – Ken

Смежные вопросы