Bonjour,
je m'essaye (débutant) à développer une classe c++ dont certains membre sont des pointeurs vers des structures définies dans une bibliothèque c.
Par exemple :
/* maclasse.hh */
#include <une_lib_en_c.h>
class MaClasse {
public:
MaClasse();
~MaClasse();
int init();
private:
ma_struct_c *s; //struct définie dans une_lib_en_c.h
};
MaClasse::MaClasse() {
s = nullptr;
}
MaClasse::~MaClasse() {
if (s != nullptr) {
// s_destroy définie dans une_lib_en_c.h
s_destroy(&s);
delete s;
}
}
// Comme il semble que ce n'est pas possible/propre de vérifier
// la bonne exécution de l'allocation dynamique de s dans le constructeur,
// fonction d'initialisation avec éventuelle erreur en retour.
int MaClasse::init() {
s = new ma_struct_c;
if (s != nullptr) {
// s_init définie dans une_lib_en_c.h
// retourne 0 en cas de succès.
return s_init(s);
}
else {
return 1;
}
}
Quelques questions :
Est-ce que c'est propre/utile/important de remplacer les malloc/free par des new/delete ?
Est-ce que cette implémentation est correcte pour la bonne libération de la mémoire allouée dynamiquement dans MaClasse::init()
lorsque le destructeur de MaClasse est appelé ?
Bonus : Puisque j'ai au moins un pointeur comme variable membre, je suppose que je dois faire gaffe à redéfinir le constructeur de copie de MaClasse ?
Merci de vos conseils ou suggestions.
# quelques éléments de réponse
Posté par nazcafan . Évalué à 4. Dernière modification le 21 avril 2017 à 17:15.
new
VSmalloc
pour allouer unestruct
C, je n'ai pas vraiment d'avis ; tant que tu les utilises correctement, ça na va pas changer l'univers.new
peut utiliser un autre allocateur quemalloc
mais tant que tu ne mélanges pasmalloc
/free
avecnew
/delete
sur le même objet, ça ira.new
échoue à allouer de la mémoire, il émet une exception de typestd::bad_alloc
, du coup, il est inutile de tester le pointeur de retour enversnullptr
.public
du genreinit()
. Ça laisse plus d'opportunités à l'utilisateur de la classe de se tirer une balle dans le pied. Je suis partisan d'insérer le code d'init
dans le constructeur et de balancer une exception en cas d'erreur. Au moins l'utilisateur ne se retrouvera jamais avec un objet inutilisable.Deleter
idoine.[^] # Re: quelques éléments de réponse
Posté par audionuma (site web personnel, Mastodon) . Évalué à 2.
Merci de la réponse.
Ben, c'était mon plan de départ, mais j'ai cru comprendre (peut-être par erreur) que le lancement d'une exception dans un constructeur laisse la construction de l'objet inachevée, et que donc son destructeur n'est pas appelé au moment opportun. Si j'avais déjà alloué des trucs au moment du lancement de l'exception, il faut que je fasse le ménage avant de lancer l'exception ?
Cela m'intéresse aussi, mais, hum, ça sera pour la deuxième semaine. Comment utiliser un
unique_ptr
si je dois passer ce pointeur à une fonction C qui le modifie (ou qui modifie la valeur de certains membres de la structure pointée) ?[^] # Re: quelques éléments de réponse
Posté par nazcafan . Évalué à 3. Dernière modification le 22 avril 2017 à 00:47.
C'est une question pertinente. Ce que tu décris est juste, mais une nuance t'échappe. Effectivement, si le constructeur d'un objet émet une exception, l'objet n'est pas considéré comme construit et son destructeur n'est pas appelé. Toutefois, le destructeur des membres et le destructeur des classes de base sont appelés.
Ce qui m'amène à une réponse plus complète : effectivement, si une exception est lancée depuis le constructeur au milieu de l'allocation de différentes ressources, les libérer proprement risque d'être particulièrement ardu. En conséquence, il est fortement déconseillé pour une classe C++ de gérer directement plus d'une seule ressource. Les autres ressources doivent être gérées par des membres ou des classes de base, dont le destructeur (et donc la libération de la ressource) sera appelé automatiquement par le langage, même dans le cas où l'exception est lancée dans le constructeur.
[^] # Re: quelques éléments de réponse
Posté par nazcafan . Évalué à 2.
Tu peux utiliser unique_ptr::get :
[^] # Re: quelques éléments de réponse
Posté par audionuma (site web personnel, Mastodon) . Évalué à 1.
Donc par exemple :
et
Dans quel cas faut-il implémenter un
deleter
pour ununique_ptr
? Notamment si je dois nettoyer la structure pointée avant qu'elle soit libérée ? Ledeleter
par défaut est appelé par le destructeur de MaClasse ?Merci.
[^] # Re: quelques éléments de réponse
Posté par nazcafan . Évalué à 5.
Le deleter par défaut est appelé par le destructeur de
unique_ptr
après que le destructeur de ta classe a fini de s'exécuter (cf ma première réponse). En l’occurrence, tu pourrais le remplacer par un Deleter qui appelle la fonction de nettoyage C avant de libérer la mémoire :C'est une approche un peu bâtarde à mon goût parce que les responsabilités d'initialisation et de de nettoyage C ne sont pas du ressort du même objet : l'initialisation est faite dans le constructeur de MyClass et le nettoyage dans le Deleter du
unique_ptr
. Du point de vue ingénierie, ce n'est pas particulièrement idéal, d'ailleurs tu noteras que le code de nettoyage C est lancé même si l'initialisation échoue. Ça dépend probablement de ton API C, mais en général, c'est inutile, voire dangereux. Pour le coup, il serait peut-être préférable de laisser la responsabilité de gérer la mémoire auunique_ptr
et de laisser à ta classe de gérer l'initialisation / nettoyage :Ici, le code de nettoyage C n'est appelé que si l'initialisation C a abouti.
Le
catch
va récupérer toutes les erreurs, notamment les erreurs d'allocation, et l'exception due à une mauvaise initialisation en C. Mais au moins toutes les ressources auront déjà été libérées au moment ducatch
, en gros, à ce niveau tu n'as plus qu'à logguer, tout l reste est déjà « géré ». Le fait de confier la libération des ressources aux destructeurs des objets te permet d'avoir une logiquetry
-catch
relativement simple comparé à d'autres langages.[^] # Re: quelques éléments de réponse
Posté par audionuma (site web personnel, Mastodon) . Évalué à 1.
Merci pour toutes ces précisions.
J'ai de quoi réfléchir pour un moment …
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.