Suppression sécurisée de la charge d’object d’événement C ++ avec shared_ptr

J’ai besoin de créer un object Event pour être dissortingbué par un système d’écoute d’événement. L’ Event doit avoir les propriétés suivantes:

  1. Event peut potentiellement être géré par des objects écouteurs 0..n.
  2. Event contient un pointeur vide qui peut pointer sur un object arbitraire (charge utile) (type inconnu au moment de la construction). Le programme d’écoute d’événement sera converti au type approprié en fonction du nom de l’ Event .
  3. Il faut que l’object de données utiles soit (automatiquement) supprimé une fois que l’événement a été envoyé aux parties intéressées. Le générateur d’événement d’origine ne peut pas être désalloué car l’événement se place dans une queue asvnc.
  4. Supposons que les auditeurs puissent effectuer une copie superficielle de la charge utile lorsque l’événement est traité.

J’ai implémenté la solution ici , mais AFAIK, cela provoque la désallocation de la charge utile (via unique_ptr ) après le premier gestionnaire d’événements.

Dans le code ci-dessous, ‘setData’ tente de récupérer l’object payload ( dataObject ) et de le convertir en un shared_ptr destiné à être porté par void* data . getData fait le “reverse”:

 class Event { public: std::ssortingng name; Event(std::ssortingng n = "Unknown", void* d = nullptr) :name(n), data(d) {} template void setData(const T dataObject) { //Create a new object to store the data, pointed to by smart pointer std::shared_ptr 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(object_ptr); } //reverse of setData. template T getData() const { std::unique_ptr< std::shared_ptr > data_ptr((std::shared_ptr*) data); std::shared_ptr object_ptr = *data_ptr; return *object_ptr; } private: void* data; }; 

Vous devriez envisager std::any au lieu de void* . Cela éviterait une allocation de mémoire complexe pour les data . Si vous ne pouvez pas utiliser C ++ 17, ce n’est pas si difficile de créer votre propre implémentation à partir du papier de Kevlin Henney (ajoutez des parties manquantes à la spécification C ++ 17, par exemple le constructeur de déplacement).

Votre code peut devenir quelque chose comme ça:

 class Event { public: std::ssortingng name; Event() :name("Unknown") {} template Event(std::ssortingng n, T&& dataObject) :name(n) { setData(std::forward(dataObject)); } template void setData(T&& dataObject) { using data_t = typename std::decay::type; data = std::make_shared(std::forward(dataObject)); } //reverse of setData. template T getData() const { using data_t = typename std::decay::type; return *any_cast>(data); } private: any data; }; 

J’ai utilisé dans mon code des références à lvalue dans les méthodes de modèle pour éviter les surcharges: la déduction de modèle permet à la même méthode d’accepter des variables nommées ainsi que des valeurs temporaires, avec ou sans constance. Voir ici pour plus de détails.

std :: forward est utilisé pour effectuer une transmission parfaite . En effet, si vous construisez un Event partir d’une valeur telle que celle-ci:

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

L’appel de setData sans transfert parfait transmettra dataObject tant que lvalue puisqu’il s’agit d’une variable nommée dans ce contexte:

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

Le transfert parfait transmettra dataObject tant que rvalue, mais uniquement s’il a été construit à partir d’une rvalue:

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

Si dataObject a été construit à partir d’une lvalue, le même std::forward le transmettra en tant que lvalue et générera l’invocation du constructeur de la copie, comme souhaité:

 Foo f{}; Event e{"Hello", f}; // ... setData(std::forward(dataObject)); // will call Foo's copy constructor 

Démo complète


Si vous souhaitez continuer à utiliser les pointeurs pour void , vous pouvez intégrer un fichier de suppression approprié avec le shared_ptr :

 template void setData(T&& dataObject) { using data_t = typename std::decay::type; data = std::shared_ptr( new data_t{std::forward(dataObject)}, [](void* ptr) { delete static_cast(ptr); } ); } 

Avec les data déclarées comme shared_ptr et getData :

 template T getData() const { using data_t = typename std::decay::type; return *std::static_pointer_cast(data); }