Bonsoir.
Je voudrais obtenir quelques avis d'experts sur un problème épineux.
Considérons que vous livrez une bibliothèque de sources C++ (avec des
templates, comme ça on ne parle pas de forme compilée).
Dans cette bibliothèque se trouve un sous-ensemble qui est un framework de
support pour vos classes, fournissant des services.
Par exemple, imaginons que vous fournissez des gestionnaires de mémoire
particuliers (heaps). De tels objets doivent bien sûr être instanciés avant
tout client et terminés après destruction de tout client. On va supposer en
plus que vous ne voulez pas imposer à votre utilisateur d'invoquer une
fonction Init() et une fonction Cleanup() pour votre bibliothèque.
Donc, et même si certains risquent de dire que c'est sale, une bonne méthode
a priori semble de déclarer les objets de votre framework comme globaux,
afin de bénéficier de l'invocation automatique des constructeurs et
destructeurs... Et donc vous les mettez dans le module qui fournit les
fonctions membres de vos classes.
Mais là où ça .erde, c'est que rien ne doit interdire à votre utilisateur
d'utiliser lui-même appel des objets globaux construits à partir de
certaines de vos classes...
Et alors, de manière aléatoire selon l'ordre d'assemblage des modules
compilés, les objets de l'utilisateur sont construits avant le framework et
donc l'utilisent à l'état non encore construit !!! Et inversement pour la
destruction finale :-[
Premier élement de solution: je change un peu de stratégie en allouant les
objets du framework dynamiquement, ils sont alors représentés par des
pointeurs static initialisés à NULL, et ils sont créés à la première
tentative d'utilisation.
Pour ça je fais appel à un objet "manager", sans données internes afin que
lui supporte de ne pas avoir été construit, et qui va fournir une méthode
static pour accéder au framework, après l'avoir construit si nécessaire.
Méthode qu'il invoque lui-même dans son constructeur, au cas où le problème
qui nous occupe ne se serait pas posé.
Ainsi c'est bon pour la construction :-)
Mais comment alors gérer la nécessaire destruction du framework ?
On ne peut pas compter sur le destructeur du "manager", car dans le cas qui
fâche, où il est construit trop tard, il sera aussi détruit trop tôt !
Se lancer dans une histoire de lock-count ? Ca veut dire compter sur
l'infaillibilité de son utilisateur :-/
La solution que j'ai trouvée fait appel à la bonne vieille fonction atexit()
de la stdlib, par laquelle je définis une callback pour assurer la
destruction de mon framework à l'exit-time, donc après destruction de tous
les objets globaux (elle invoque en fait une autre méthode du "manager",
static publique mais locale au module, et c'est aussi le "manager" qui la
définit, pour faire un peu propre).
Ca marche très bien, mais je me pose des questions, notamment en cas de
contexte multithread (la bibliothèque elle-même est thread-safe).
A priori, la callback est invoquée lors de la terminaison du thread
principal, c'est à dire après arrêt de tous les autres, et donc destruction
de tous les objets qu'ils ont pu construire si on termine proprement.
Donc ça doit être bon...
Qu'en pensez-vous ? Vous faites comment dans ce genre de situation ?
Bonsoir.
Je voudrais obtenir quelques avis d'experts sur un problème épineux.
Considérons que vous livrez une bibliothèque de sources C++ (avec des
templates, comme ça on ne parle pas de forme compilée).
Dans cette bibliothèque se trouve un sous-ensemble qui est un framework de
support pour vos classes, fournissant des services.
Par exemple, imaginons que vous fournissez des gestionnaires de mémoire
particuliers (heaps). De tels objets doivent bien sûr être instanciés avant
tout client et terminés après destruction de tout client. On va supposer en
plus que vous ne voulez pas imposer à votre utilisateur d'invoquer une
fonction Init() et une fonction Cleanup() pour votre bibliothèque.
Donc, et même si certains risquent de dire que c'est sale, une bonne méthode
a priori semble de déclarer les objets de votre framework comme globaux,
afin de bénéficier de l'invocation automatique des constructeurs et
destructeurs... Et donc vous les mettez dans le module qui fournit les
fonctions membres de vos classes.
Mais là où ça .erde, c'est que rien ne doit interdire à votre utilisateur
d'utiliser lui-même appel des objets globaux construits à partir de
certaines de vos classes...
Et alors, de manière aléatoire selon l'ordre d'assemblage des modules
compilés, les objets de l'utilisateur sont construits avant le framework et
donc l'utilisent à l'état non encore construit !!! Et inversement pour la
destruction finale :-[
Premier élement de solution: je change un peu de stratégie en allouant les
objets du framework dynamiquement, ils sont alors représentés par des
pointeurs static initialisés à NULL, et ils sont créés à la première
tentative d'utilisation.
Pour ça je fais appel à un objet "manager", sans données internes afin que
lui supporte de ne pas avoir été construit, et qui va fournir une méthode
static pour accéder au framework, après l'avoir construit si nécessaire.
Méthode qu'il invoque lui-même dans son constructeur, au cas où le problème
qui nous occupe ne se serait pas posé.
Ainsi c'est bon pour la construction :-)
Mais comment alors gérer la nécessaire destruction du framework ?
On ne peut pas compter sur le destructeur du "manager", car dans le cas qui
fâche, où il est construit trop tard, il sera aussi détruit trop tôt !
Se lancer dans une histoire de lock-count ? Ca veut dire compter sur
l'infaillibilité de son utilisateur :-/
La solution que j'ai trouvée fait appel à la bonne vieille fonction atexit()
de la stdlib, par laquelle je définis une callback pour assurer la
destruction de mon framework à l'exit-time, donc après destruction de tous
les objets globaux (elle invoque en fait une autre méthode du "manager",
static publique mais locale au module, et c'est aussi le "manager" qui la
définit, pour faire un peu propre).
Ca marche très bien, mais je me pose des questions, notamment en cas de
contexte multithread (la bibliothèque elle-même est thread-safe).
A priori, la callback est invoquée lors de la terminaison du thread
principal, c'est à dire après arrêt de tous les autres, et donc destruction
de tous les objets qu'ils ont pu construire si on termine proprement.
Donc ça doit être bon...
Qu'en pensez-vous ? Vous faites comment dans ce genre de situation ?
Bonsoir.
Je voudrais obtenir quelques avis d'experts sur un problème épineux.
Considérons que vous livrez une bibliothèque de sources C++ (avec des
templates, comme ça on ne parle pas de forme compilée).
Dans cette bibliothèque se trouve un sous-ensemble qui est un framework de
support pour vos classes, fournissant des services.
Par exemple, imaginons que vous fournissez des gestionnaires de mémoire
particuliers (heaps). De tels objets doivent bien sûr être instanciés avant
tout client et terminés après destruction de tout client. On va supposer en
plus que vous ne voulez pas imposer à votre utilisateur d'invoquer une
fonction Init() et une fonction Cleanup() pour votre bibliothèque.
Donc, et même si certains risquent de dire que c'est sale, une bonne méthode
a priori semble de déclarer les objets de votre framework comme globaux,
afin de bénéficier de l'invocation automatique des constructeurs et
destructeurs... Et donc vous les mettez dans le module qui fournit les
fonctions membres de vos classes.
Mais là où ça .erde, c'est que rien ne doit interdire à votre utilisateur
d'utiliser lui-même appel des objets globaux construits à partir de
certaines de vos classes...
Et alors, de manière aléatoire selon l'ordre d'assemblage des modules
compilés, les objets de l'utilisateur sont construits avant le framework et
donc l'utilisent à l'état non encore construit !!! Et inversement pour la
destruction finale :-[
Premier élement de solution: je change un peu de stratégie en allouant les
objets du framework dynamiquement, ils sont alors représentés par des
pointeurs static initialisés à NULL, et ils sont créés à la première
tentative d'utilisation.
Pour ça je fais appel à un objet "manager", sans données internes afin que
lui supporte de ne pas avoir été construit, et qui va fournir une méthode
static pour accéder au framework, après l'avoir construit si nécessaire.
Méthode qu'il invoque lui-même dans son constructeur, au cas où le problème
qui nous occupe ne se serait pas posé.
Ainsi c'est bon pour la construction :-)
Mais comment alors gérer la nécessaire destruction du framework ?
On ne peut pas compter sur le destructeur du "manager", car dans le cas qui
fâche, où il est construit trop tard, il sera aussi détruit trop tôt !
Se lancer dans une histoire de lock-count ? Ca veut dire compter sur
l'infaillibilité de son utilisateur :-/
La solution que j'ai trouvée fait appel à la bonne vieille fonction atexit()
de la stdlib, par laquelle je définis une callback pour assurer la
destruction de mon framework à l'exit-time, donc après destruction de tous
les objets globaux (elle invoque en fait une autre méthode du "manager",
static publique mais locale au module, et c'est aussi le "manager" qui la
définit, pour faire un peu propre).
Ca marche très bien, mais je me pose des questions, notamment en cas de
contexte multithread (la bibliothèque elle-même est thread-safe).
A priori, la callback est invoquée lors de la terminaison du thread
principal, c'est à dire après arrêt de tous les autres, et donc destruction
de tous les objets qu'ils ont pu construire si on termine proprement.
Donc ça doit être bon...
Qu'en pensez-vous ? Vous faites comment dans ce genre de situation ?
La solution que j'ai trouvée fait appel à la bonne vieille
fonction atexit() de la stdlib, par laquelle je définis une
callback pour assurer la destruction de mon framework à
l'exit-time, donc après destruction de tous les objets globaux
(elle invoque en fait une autre méthode du "manager", static
publique mais locale au module, et c'est aussi le "manager"
qui la définit, pour faire un peu propre).
Ca marche très bien,
mais je me pose des questions, notamment en cas de contexte
multithread (la bibliothèque elle-même est thread-safe). A
priori, la callback est invoquée lors de la terminaison du
thread principal, c'est à dire après arrêt de tous les autres,
et donc destruction de tous les objets qu'ils ont pu
construire si on termine proprement. Donc ça doit être bon...
Qu'en pensez-vous ? Vous faites comment dans ce genre de
situation ?
La solution que j'ai trouvée fait appel à la bonne vieille
fonction atexit() de la stdlib, par laquelle je définis une
callback pour assurer la destruction de mon framework à
l'exit-time, donc après destruction de tous les objets globaux
(elle invoque en fait une autre méthode du "manager", static
publique mais locale au module, et c'est aussi le "manager"
qui la définit, pour faire un peu propre).
Ca marche très bien,
mais je me pose des questions, notamment en cas de contexte
multithread (la bibliothèque elle-même est thread-safe). A
priori, la callback est invoquée lors de la terminaison du
thread principal, c'est à dire après arrêt de tous les autres,
et donc destruction de tous les objets qu'ils ont pu
construire si on termine proprement. Donc ça doit être bon...
Qu'en pensez-vous ? Vous faites comment dans ce genre de
situation ?
La solution que j'ai trouvée fait appel à la bonne vieille
fonction atexit() de la stdlib, par laquelle je définis une
callback pour assurer la destruction de mon framework à
l'exit-time, donc après destruction de tous les objets globaux
(elle invoque en fait une autre méthode du "manager", static
publique mais locale au module, et c'est aussi le "manager"
qui la définit, pour faire un peu propre).
Ca marche très bien,
mais je me pose des questions, notamment en cas de contexte
multithread (la bibliothèque elle-même est thread-safe). A
priori, la callback est invoquée lors de la terminaison du
thread principal, c'est à dire après arrêt de tous les autres,
et donc destruction de tous les objets qu'ils ont pu
construire si on termine proprement. Donc ça doit être bon...
Qu'en pensez-vous ? Vous faites comment dans ce genre de
situation ?
[...]
Et alors, de manière aléatoire selon l'ordre d'assemblage des
modules compilés, les objets de l'utilisateur sont construits avant
le framework et donc l'utilisent à l'état non encore construit !!!
Et inversement pour la destruction finale :-[
La solution est un idiome courant de C++. L'instantiation des
globales n'est pas spécifiée mais l'intanciation des statiques de
fonction oui:
au premier apel de la fonction. Donc il suffit d'utiliser des
fonctions à la place des globales.
MyObject* globale_ressource()
{
static MyObject* my_object=new MyObjext(1,2,3,4);
return my_object;
}
puis:
globale_ressource()->method();
Pour la destruction, tu laisses faire le ramasse-miette.
[...]
Nécessaire, c'et beaucoup dire. L'OS s'en occupe tout seul en général.
Les seuls objets qui posent éventuellement problème sont ceux qui
spawn des threads mais ceux là sont en général explicitement gérés
(ie. prévenus de la fermeture du framework).
Pour les cas où cela serait vraiment nécessaire, par exemple pour
rendre une ressource non géré par le système, tu pourrais utiliser un
smart pointer dans la static.
[...]
D'un autre coté, si ton pointeur global est déjà détruit et mis à NULL
(avec un scoped_ptr par exemple), les utilisateurs savent que
l'objet et inutilisable et cela peut être documenté.
Ca marche très bien,
mais je me pose des questions, notamment en cas de
contexte multithread (la bibliothèque elle-même est thread-safe).
A priori, la callback est invoquée lors de la terminaison du thread
principal, c'est à dire après arrêt de tous les autres, et donc
destruction de tous les objets qu'ils ont pu construire si on termine
proprement.
Donc ça doit être bon...
Rien ne t'assure que les threads se sont terminés en rendant leurs
ressource donc si il tesuffit de savoir que rien ne va invoquer ton
objet, ça a l'air correct. A moins que l'utilisateur de la bibliothèque
ait aussi cette idée, mais bon, là c'est de sa faute :)
Qu'en pensez-vous ? Vous faites comment dans ce genre
de situation ?
Ca depend des besoins.
Je pourrais aussi redéfinir l'opérateur new pour les objets qui ont
besoin d'une allocation spécifique, peut être avec un système de
stratégie d'allocation, et ne pas exposer un allocateur global. Enfin,
ça vaudrait vraiment le coup pour les objet à allocation/deallocation
intensive en environment multithread pour éviter la sérialisaion.
Pas le genre de chose que je fais tous les jour.
[...]
Et alors, de manière aléatoire selon l'ordre d'assemblage des
modules compilés, les objets de l'utilisateur sont construits avant
le framework et donc l'utilisent à l'état non encore construit !!!
Et inversement pour la destruction finale :-[
La solution est un idiome courant de C++. L'instantiation des
globales n'est pas spécifiée mais l'intanciation des statiques de
fonction oui:
au premier apel de la fonction. Donc il suffit d'utiliser des
fonctions à la place des globales.
MyObject* globale_ressource()
{
static MyObject* my_object=new MyObjext(1,2,3,4);
return my_object;
}
puis:
globale_ressource()->method();
Pour la destruction, tu laisses faire le ramasse-miette.
[...]
Nécessaire, c'et beaucoup dire. L'OS s'en occupe tout seul en général.
Les seuls objets qui posent éventuellement problème sont ceux qui
spawn des threads mais ceux là sont en général explicitement gérés
(ie. prévenus de la fermeture du framework).
Pour les cas où cela serait vraiment nécessaire, par exemple pour
rendre une ressource non géré par le système, tu pourrais utiliser un
smart pointer dans la static.
[...]
D'un autre coté, si ton pointeur global est déjà détruit et mis à NULL
(avec un scoped_ptr par exemple), les utilisateurs savent que
l'objet et inutilisable et cela peut être documenté.
Ca marche très bien,
mais je me pose des questions, notamment en cas de
contexte multithread (la bibliothèque elle-même est thread-safe).
A priori, la callback est invoquée lors de la terminaison du thread
principal, c'est à dire après arrêt de tous les autres, et donc
destruction de tous les objets qu'ils ont pu construire si on termine
proprement.
Donc ça doit être bon...
Rien ne t'assure que les threads se sont terminés en rendant leurs
ressource donc si il tesuffit de savoir que rien ne va invoquer ton
objet, ça a l'air correct. A moins que l'utilisateur de la bibliothèque
ait aussi cette idée, mais bon, là c'est de sa faute :)
Qu'en pensez-vous ? Vous faites comment dans ce genre
de situation ?
Ca depend des besoins.
Je pourrais aussi redéfinir l'opérateur new pour les objets qui ont
besoin d'une allocation spécifique, peut être avec un système de
stratégie d'allocation, et ne pas exposer un allocateur global. Enfin,
ça vaudrait vraiment le coup pour les objet à allocation/deallocation
intensive en environment multithread pour éviter la sérialisaion.
Pas le genre de chose que je fais tous les jour.
[...]
Et alors, de manière aléatoire selon l'ordre d'assemblage des
modules compilés, les objets de l'utilisateur sont construits avant
le framework et donc l'utilisent à l'état non encore construit !!!
Et inversement pour la destruction finale :-[
La solution est un idiome courant de C++. L'instantiation des
globales n'est pas spécifiée mais l'intanciation des statiques de
fonction oui:
au premier apel de la fonction. Donc il suffit d'utiliser des
fonctions à la place des globales.
MyObject* globale_ressource()
{
static MyObject* my_object=new MyObjext(1,2,3,4);
return my_object;
}
puis:
globale_ressource()->method();
Pour la destruction, tu laisses faire le ramasse-miette.
[...]
Nécessaire, c'et beaucoup dire. L'OS s'en occupe tout seul en général.
Les seuls objets qui posent éventuellement problème sont ceux qui
spawn des threads mais ceux là sont en général explicitement gérés
(ie. prévenus de la fermeture du framework).
Pour les cas où cela serait vraiment nécessaire, par exemple pour
rendre une ressource non géré par le système, tu pourrais utiliser un
smart pointer dans la static.
[...]
D'un autre coté, si ton pointeur global est déjà détruit et mis à NULL
(avec un scoped_ptr par exemple), les utilisateurs savent que
l'objet et inutilisable et cela peut être documenté.
Ca marche très bien,
mais je me pose des questions, notamment en cas de
contexte multithread (la bibliothèque elle-même est thread-safe).
A priori, la callback est invoquée lors de la terminaison du thread
principal, c'est à dire après arrêt de tous les autres, et donc
destruction de tous les objets qu'ils ont pu construire si on termine
proprement.
Donc ça doit être bon...
Rien ne t'assure que les threads se sont terminés en rendant leurs
ressource donc si il tesuffit de savoir que rien ne va invoquer ton
objet, ça a l'air correct. A moins que l'utilisateur de la bibliothèque
ait aussi cette idée, mais bon, là c'est de sa faute :)
Qu'en pensez-vous ? Vous faites comment dans ce genre
de situation ?
Ca depend des besoins.
Je pourrais aussi redéfinir l'opérateur new pour les objets qui ont
besoin d'une allocation spécifique, peut être avec un système de
stratégie d'allocation, et ne pas exposer un allocateur global. Enfin,
ça vaudrait vraiment le coup pour les objet à allocation/deallocation
intensive en environment multithread pour éviter la sérialisaion.
Pas le genre de chose que je fais tous les jour.
On Dec 13, 6:51 pm, "Patrick 'Zener' Brunet"
wrote:
Michael a déjà dit ce que j'avais à dire, mais un point :
[...]La solution que j'ai trouvée fait appel à la bonne vieille
fonction atexit() de la stdlib, par laquelle je définis une
callback pour assurer la destruction de mon framework à
l'exit-time, donc après destruction de tous les objets globaux
(elle invoque en fait une autre méthode du "manager", static
publique mais locale au module, et c'est aussi le "manager"
qui la définit, pour faire un peu propre).
Selon la norme, les fonctions inscrites avec atexit sont
appelées au même temps que les destructeurs -- les fonctions
inscrites avant la construction d'un objet statique après sa
destruction, et les fonctions inscrites après la construction
d'un object avant sa destruction. Tu as donc le même
problème qu'avec les constructeurs.
Ca marche très bien,
Tu en es sûr ? Avec toutes les implémentations.
En passant, g++ documente qu'il ne sait pas implémenter ça
correctement sur toutes les plateformes. Sans doute parce que
pour l'implémenter correctement, il faut que le compilateur et
la bibliothèque avec atexit collaborent, et que sur certaines
plate-formes, comme Solaris, la fonction atexit fait partie
d'une bibliothèque livrée avec le système, sur laquelle g++ n'a
pas d'influence. Si tu as testé dans un tel environement, tout
ce que tu as démontré, c'est que ça marche avec une
implémentation non conforme.
mais je me pose des questions, notamment en cas de
contexte multithread (la bibliothèque elle-même est
thread-safe). A priori, la callback est invoquée lors de
la terminaison du thread principal, c'est à dire après arrêt
de tous les autres, et donc destruction de tous les objets
qu'ils ont pu construire si on termine proprement.
Donc ça doit être bon...
C'est une question sur laquelle Posix, en tout cas, se tait.
Sans spécifications supplémentaires, je pars du principe que
s'il y a des threads encore actifs autres que celui qui appelle
exit(), lors de l'appel d'exit(), c'est un comportement
indéfini. (Sauf, peut-être, si on peut garantir que ces threads
ne font rien. Par exemple, qu'il font partie d'un thread pool,
et sont en attente qu'on ait besoin d'eux.)
En passant, c'est aussi un argument contre la destruction
de certaines ressources. Appeler le destructeur d'un mutex
(qui lui appelera sans doute pthread_destroy, ou son
équivalent Windows) quand il y a un thread qui y attend,
ce n'est pas conseillé.
Qu'en pensez-vous ? Vous faites comment dans ce
genre de situation ?
J'utilise normalement le modèle singleton pour règler la
construction -- dans le cas d'un environement
multi-threaded, je m'arrangerai que la fonction instance()
soit appelée au moins une fois avant l'entrée dans main(),
pour éviter la nécessité des locks dans le singleton
(ou sinon, la fonction instance()
renvoie un boost::shared_ptr qui libère le lock lorsque la
dernière instance est détruite). Pour la destruction, en
général, je ne l'appele pas du tout. C'est le système qui
s'occupe de toutes ses ressources (mémoire, mutex, etc.).
On Dec 13, 6:51 pm, "Patrick 'Zener' Brunet"
<use.link.in.signat...@ddress.invalid> wrote:
Michael a déjà dit ce que j'avais à dire, mais un point :
[...]
La solution que j'ai trouvée fait appel à la bonne vieille
fonction atexit() de la stdlib, par laquelle je définis une
callback pour assurer la destruction de mon framework à
l'exit-time, donc après destruction de tous les objets globaux
(elle invoque en fait une autre méthode du "manager", static
publique mais locale au module, et c'est aussi le "manager"
qui la définit, pour faire un peu propre).
Selon la norme, les fonctions inscrites avec atexit sont
appelées au même temps que les destructeurs -- les fonctions
inscrites avant la construction d'un objet statique après sa
destruction, et les fonctions inscrites après la construction
d'un object avant sa destruction. Tu as donc le même
problème qu'avec les constructeurs.
Ca marche très bien,
Tu en es sûr ? Avec toutes les implémentations.
En passant, g++ documente qu'il ne sait pas implémenter ça
correctement sur toutes les plateformes. Sans doute parce que
pour l'implémenter correctement, il faut que le compilateur et
la bibliothèque avec atexit collaborent, et que sur certaines
plate-formes, comme Solaris, la fonction atexit fait partie
d'une bibliothèque livrée avec le système, sur laquelle g++ n'a
pas d'influence. Si tu as testé dans un tel environement, tout
ce que tu as démontré, c'est que ça marche avec une
implémentation non conforme.
mais je me pose des questions, notamment en cas de
contexte multithread (la bibliothèque elle-même est
thread-safe). A priori, la callback est invoquée lors de
la terminaison du thread principal, c'est à dire après arrêt
de tous les autres, et donc destruction de tous les objets
qu'ils ont pu construire si on termine proprement.
Donc ça doit être bon...
C'est une question sur laquelle Posix, en tout cas, se tait.
Sans spécifications supplémentaires, je pars du principe que
s'il y a des threads encore actifs autres que celui qui appelle
exit(), lors de l'appel d'exit(), c'est un comportement
indéfini. (Sauf, peut-être, si on peut garantir que ces threads
ne font rien. Par exemple, qu'il font partie d'un thread pool,
et sont en attente qu'on ait besoin d'eux.)
En passant, c'est aussi un argument contre la destruction
de certaines ressources. Appeler le destructeur d'un mutex
(qui lui appelera sans doute pthread_destroy, ou son
équivalent Windows) quand il y a un thread qui y attend,
ce n'est pas conseillé.
Qu'en pensez-vous ? Vous faites comment dans ce
genre de situation ?
J'utilise normalement le modèle singleton pour règler la
construction -- dans le cas d'un environement
multi-threaded, je m'arrangerai que la fonction instance()
soit appelée au moins une fois avant l'entrée dans main(),
pour éviter la nécessité des locks dans le singleton
(ou sinon, la fonction instance()
renvoie un boost::shared_ptr qui libère le lock lorsque la
dernière instance est détruite). Pour la destruction, en
général, je ne l'appele pas du tout. C'est le système qui
s'occupe de toutes ses ressources (mémoire, mutex, etc.).
On Dec 13, 6:51 pm, "Patrick 'Zener' Brunet"
wrote:
Michael a déjà dit ce que j'avais à dire, mais un point :
[...]La solution que j'ai trouvée fait appel à la bonne vieille
fonction atexit() de la stdlib, par laquelle je définis une
callback pour assurer la destruction de mon framework à
l'exit-time, donc après destruction de tous les objets globaux
(elle invoque en fait une autre méthode du "manager", static
publique mais locale au module, et c'est aussi le "manager"
qui la définit, pour faire un peu propre).
Selon la norme, les fonctions inscrites avec atexit sont
appelées au même temps que les destructeurs -- les fonctions
inscrites avant la construction d'un objet statique après sa
destruction, et les fonctions inscrites après la construction
d'un object avant sa destruction. Tu as donc le même
problème qu'avec les constructeurs.
Ca marche très bien,
Tu en es sûr ? Avec toutes les implémentations.
En passant, g++ documente qu'il ne sait pas implémenter ça
correctement sur toutes les plateformes. Sans doute parce que
pour l'implémenter correctement, il faut que le compilateur et
la bibliothèque avec atexit collaborent, et que sur certaines
plate-formes, comme Solaris, la fonction atexit fait partie
d'une bibliothèque livrée avec le système, sur laquelle g++ n'a
pas d'influence. Si tu as testé dans un tel environement, tout
ce que tu as démontré, c'est que ça marche avec une
implémentation non conforme.
mais je me pose des questions, notamment en cas de
contexte multithread (la bibliothèque elle-même est
thread-safe). A priori, la callback est invoquée lors de
la terminaison du thread principal, c'est à dire après arrêt
de tous les autres, et donc destruction de tous les objets
qu'ils ont pu construire si on termine proprement.
Donc ça doit être bon...
C'est une question sur laquelle Posix, en tout cas, se tait.
Sans spécifications supplémentaires, je pars du principe que
s'il y a des threads encore actifs autres que celui qui appelle
exit(), lors de l'appel d'exit(), c'est un comportement
indéfini. (Sauf, peut-être, si on peut garantir que ces threads
ne font rien. Par exemple, qu'il font partie d'un thread pool,
et sont en attente qu'on ait besoin d'eux.)
En passant, c'est aussi un argument contre la destruction
de certaines ressources. Appeler le destructeur d'un mutex
(qui lui appelera sans doute pthread_destroy, ou son
équivalent Windows) quand il y a un thread qui y attend,
ce n'est pas conseillé.
Qu'en pensez-vous ? Vous faites comment dans ce
genre de situation ?
J'utilise normalement le modèle singleton pour règler la
construction -- dans le cas d'un environement
multi-threaded, je m'arrangerai que la fonction instance()
soit appelée au moins une fois avant l'entrée dans main(),
pour éviter la nécessité des locks dans le singleton
(ou sinon, la fonction instance()
renvoie un boost::shared_ptr qui libère le lock lorsque la
dernière instance est détruite). Pour la destruction, en
général, je ne l'appele pas du tout. C'est le système qui
s'occupe de toutes ses ressources (mémoire, mutex, etc.).
Bonjour.
"Michael DOUBEZ" a écrit dans le message de news:
476199a3$0$24073$[...]
Et alors, de manière aléatoire selon l'ordre d'assemblage des
modules compilés, les objets de l'utilisateur sont construits avant
le framework et donc l'utilisent à l'état non encore construit !!!
Et inversement pour la destruction finale :-[
La solution est un idiome courant de C++. L'instantiation des
globales n'est pas spécifiée mais l'intanciation des statiques de
fonction oui:
au premier apel de la fonction. Donc il suffit d'utiliser des
fonctions à la place des globales.
MyObject* globale_ressource()
{
static MyObject* my_object=new MyObjext(1,2,3,4);
return my_object;
}
puis:
globale_ressource()->method();
Pour la destruction, tu laisses faire le ramasse-miette.
En plus, ça évite aussi d'écrire:
static
MyObject *
CManager::GetObject( void)
{
if( my_object == NULL)
my_object=new MyObjext(1,2,3,4);
return my_object;
}
donc un test à faire à chaque invocation.
Mais par contre, pas de destruction propre possible avec ça...
[...]
Nécessaire, c'et beaucoup dire. L'OS s'en occupe tout seul en général.
Les seuls objets qui posent éventuellement problème sont ceux qui
spawn des threads mais ceux là sont en général explicitement gérés
(ie. prévenus de la fermeture du framework).
Certes, mais si on compile en mode debug, on se prend des tas d'injures
disant que la bibliothèque ne sait pas restituer proprement la mémoire, et
ça gâche la qualité du produit.
Au minimum ça peut conduire à traquer des bugs qui sont en fait
"volontaires", ce qui est toujours désagréable.Pour les cas où cela serait vraiment nécessaire, par exemple pour
rendre une ressource non géré par le système, tu pourrais utiliser un
smart pointer dans la static.
C'est ce que je voulais dire en parlant de lock-count.
Bon là on perd le bénéfice du test évité dans l'accesseur...
Mais surtout c'est complètement remis en cause si l'utilisateur lui-même
gère mal ses cycles de vie. J'aimerais bien que ma bibliothèque soit un peu
blindée contre ça.
Bonjour.
"Michael DOUBEZ" <michael.doubez@free.fr> a écrit dans le message de news:
476199a3$0$24073$426a74cc@news.free.fr...
[...]
Et alors, de manière aléatoire selon l'ordre d'assemblage des
modules compilés, les objets de l'utilisateur sont construits avant
le framework et donc l'utilisent à l'état non encore construit !!!
Et inversement pour la destruction finale :-[
La solution est un idiome courant de C++. L'instantiation des
globales n'est pas spécifiée mais l'intanciation des statiques de
fonction oui:
au premier apel de la fonction. Donc il suffit d'utiliser des
fonctions à la place des globales.
MyObject* globale_ressource()
{
static MyObject* my_object=new MyObjext(1,2,3,4);
return my_object;
}
puis:
globale_ressource()->method();
Pour la destruction, tu laisses faire le ramasse-miette.
En plus, ça évite aussi d'écrire:
static
MyObject *
CManager::GetObject( void)
{
if( my_object == NULL)
my_object=new MyObjext(1,2,3,4);
return my_object;
}
donc un test à faire à chaque invocation.
Mais par contre, pas de destruction propre possible avec ça...
[...]
Nécessaire, c'et beaucoup dire. L'OS s'en occupe tout seul en général.
Les seuls objets qui posent éventuellement problème sont ceux qui
spawn des threads mais ceux là sont en général explicitement gérés
(ie. prévenus de la fermeture du framework).
Certes, mais si on compile en mode debug, on se prend des tas d'injures
disant que la bibliothèque ne sait pas restituer proprement la mémoire, et
ça gâche la qualité du produit.
Au minimum ça peut conduire à traquer des bugs qui sont en fait
"volontaires", ce qui est toujours désagréable.
Pour les cas où cela serait vraiment nécessaire, par exemple pour
rendre une ressource non géré par le système, tu pourrais utiliser un
smart pointer dans la static.
C'est ce que je voulais dire en parlant de lock-count.
Bon là on perd le bénéfice du test évité dans l'accesseur...
Mais surtout c'est complètement remis en cause si l'utilisateur lui-même
gère mal ses cycles de vie. J'aimerais bien que ma bibliothèque soit un peu
blindée contre ça.
Bonjour.
"Michael DOUBEZ" a écrit dans le message de news:
476199a3$0$24073$[...]
Et alors, de manière aléatoire selon l'ordre d'assemblage des
modules compilés, les objets de l'utilisateur sont construits avant
le framework et donc l'utilisent à l'état non encore construit !!!
Et inversement pour la destruction finale :-[
La solution est un idiome courant de C++. L'instantiation des
globales n'est pas spécifiée mais l'intanciation des statiques de
fonction oui:
au premier apel de la fonction. Donc il suffit d'utiliser des
fonctions à la place des globales.
MyObject* globale_ressource()
{
static MyObject* my_object=new MyObjext(1,2,3,4);
return my_object;
}
puis:
globale_ressource()->method();
Pour la destruction, tu laisses faire le ramasse-miette.
En plus, ça évite aussi d'écrire:
static
MyObject *
CManager::GetObject( void)
{
if( my_object == NULL)
my_object=new MyObjext(1,2,3,4);
return my_object;
}
donc un test à faire à chaque invocation.
Mais par contre, pas de destruction propre possible avec ça...
[...]
Nécessaire, c'et beaucoup dire. L'OS s'en occupe tout seul en général.
Les seuls objets qui posent éventuellement problème sont ceux qui
spawn des threads mais ceux là sont en général explicitement gérés
(ie. prévenus de la fermeture du framework).
Certes, mais si on compile en mode debug, on se prend des tas d'injures
disant que la bibliothèque ne sait pas restituer proprement la mémoire, et
ça gâche la qualité du produit.
Au minimum ça peut conduire à traquer des bugs qui sont en fait
"volontaires", ce qui est toujours désagréable.Pour les cas où cela serait vraiment nécessaire, par exemple pour
rendre une ressource non géré par le système, tu pourrais utiliser un
smart pointer dans la static.
C'est ce que je voulais dire en parlant de lock-count.
Bon là on perd le bénéfice du test évité dans l'accesseur...
Mais surtout c'est complètement remis en cause si l'utilisateur lui-même
gère mal ses cycles de vie. J'aimerais bien que ma bibliothèque soit un peu
blindée contre ça.
"Michael DOUBEZ" a écrit dans
le message de news:
476199a3$0$24073$[...]
Et alors, de manière aléatoire selon l'ordre d'assemblage des
modules compilés, les objets de l'utilisateur sont construits avant
le framework et donc l'utilisent à l'état non encore construit !!!
Et inversement pour la destruction finale :-[
La solution est un idiome courant de C++. L'instantiation des
globales n'est pas spécifiée mais l'intanciation des statiques de
fonction oui:
au premier apel de la fonction. Donc il suffit d'utiliser des
fonctions à la place des globales.
MyObject* globale_ressource()
{
static MyObject* my_object=new MyObjext(1,2,3,4);
return my_object;
}
[...]
Mais par contre, pas de destruction propre possible avec ça...
Je ne vois pas en quoi cela enpèche la destruction de l'objet -
avec un smart_pointer par exemple:
MyObject* globale_ressource()
{
static boost::scoped_ptr<MyObject>
my_object(new MyObjext(1,2,3,4));
return my_object.get();
}
Ou si tu veux la gèrer de manière explicite en utilisant la fonction
originelle, tu défini la globale:
static boost::scoped_ptr<MyObject>
my_object_handler(globale_ressource());
Puis
my_object_handler.reset(); //Pour une destruction explicite.
Sinon, l'objet sera détruit à la destruction de my_object_handler.
[...]
Nécessaire, c'et beaucoup dire. L'OS s'en occupe tout seul
en général.
Les seuls objets qui posent éventuellement problème sont
ceux qui spawn des threads mais ceux là sont en général
explicitement gérés (ie. prévenus de la fermeture du framework).
Certes, mais si on compile en mode debug, on se prend des tas
d'injures disant que la bibliothèque ne sait pas restituer proprement
la mémoire, et ça gâche la qualité du produit.
Au minimum ça peut conduire à traquer des bugs qui sont en fait
"volontaires", ce qui est toujours désagréable.Pour les cas où cela serait vraiment nécessaire, par exemple
pour rendre une ressource non géré par le système, tu pourrais
utiliser un smart pointer dans la static.
C'est ce que je voulais dire en parlant de lock-count.
Bon là on perd le bénéfice du test évité dans l'accesseur...
De toutes façon, en environement multithreadé, le lock count ne te
servira à rien. Il suffit d'un seul tread qui se termine de façon
anormale (ie. sans dépiler sa pile si tu utilise RAII) pour le compte
soit faussé.
Peut être le plus simple est d'utiliser un smart_pointer dans la
fonction d'accès, si l'objet est déjà détruit, il renverra NULL
et ton destructor custom agira en conséquence.
Pour l'utilisateur, il suffit de mettre dans la doc que tu n'assure pas
l'utilisation des zones de mémoire allouées par ton objet après la
sortie de main() mais que rendre la zone mémoire (delete) est
robuste ensuite, à lui de gèrer ses destructeurs pour qu'ils
n'utilisent pas cette mémoire.
"Michael DOUBEZ" <michael.doubez@free.fr> a écrit dans
le message de news:
476199a3$0$24073$426a74cc@news.free.fr...
[...]
Et alors, de manière aléatoire selon l'ordre d'assemblage des
modules compilés, les objets de l'utilisateur sont construits avant
le framework et donc l'utilisent à l'état non encore construit !!!
Et inversement pour la destruction finale :-[
La solution est un idiome courant de C++. L'instantiation des
globales n'est pas spécifiée mais l'intanciation des statiques de
fonction oui:
au premier apel de la fonction. Donc il suffit d'utiliser des
fonctions à la place des globales.
MyObject* globale_ressource()
{
static MyObject* my_object=new MyObjext(1,2,3,4);
return my_object;
}
[...]
Mais par contre, pas de destruction propre possible avec ça...
Je ne vois pas en quoi cela enpèche la destruction de l'objet -
avec un smart_pointer par exemple:
MyObject* globale_ressource()
{
static boost::scoped_ptr<MyObject>
my_object(new MyObjext(1,2,3,4));
return my_object.get();
}
Ou si tu veux la gèrer de manière explicite en utilisant la fonction
originelle, tu défini la globale:
static boost::scoped_ptr<MyObject>
my_object_handler(globale_ressource());
Puis
my_object_handler.reset(); //Pour une destruction explicite.
Sinon, l'objet sera détruit à la destruction de my_object_handler.
[...]
Nécessaire, c'et beaucoup dire. L'OS s'en occupe tout seul
en général.
Les seuls objets qui posent éventuellement problème sont
ceux qui spawn des threads mais ceux là sont en général
explicitement gérés (ie. prévenus de la fermeture du framework).
Certes, mais si on compile en mode debug, on se prend des tas
d'injures disant que la bibliothèque ne sait pas restituer proprement
la mémoire, et ça gâche la qualité du produit.
Au minimum ça peut conduire à traquer des bugs qui sont en fait
"volontaires", ce qui est toujours désagréable.
Pour les cas où cela serait vraiment nécessaire, par exemple
pour rendre une ressource non géré par le système, tu pourrais
utiliser un smart pointer dans la static.
C'est ce que je voulais dire en parlant de lock-count.
Bon là on perd le bénéfice du test évité dans l'accesseur...
De toutes façon, en environement multithreadé, le lock count ne te
servira à rien. Il suffit d'un seul tread qui se termine de façon
anormale (ie. sans dépiler sa pile si tu utilise RAII) pour le compte
soit faussé.
Peut être le plus simple est d'utiliser un smart_pointer dans la
fonction d'accès, si l'objet est déjà détruit, il renverra NULL
et ton destructor custom agira en conséquence.
Pour l'utilisateur, il suffit de mettre dans la doc que tu n'assure pas
l'utilisation des zones de mémoire allouées par ton objet après la
sortie de main() mais que rendre la zone mémoire (delete) est
robuste ensuite, à lui de gèrer ses destructeurs pour qu'ils
n'utilisent pas cette mémoire.
"Michael DOUBEZ" a écrit dans
le message de news:
476199a3$0$24073$[...]
Et alors, de manière aléatoire selon l'ordre d'assemblage des
modules compilés, les objets de l'utilisateur sont construits avant
le framework et donc l'utilisent à l'état non encore construit !!!
Et inversement pour la destruction finale :-[
La solution est un idiome courant de C++. L'instantiation des
globales n'est pas spécifiée mais l'intanciation des statiques de
fonction oui:
au premier apel de la fonction. Donc il suffit d'utiliser des
fonctions à la place des globales.
MyObject* globale_ressource()
{
static MyObject* my_object=new MyObjext(1,2,3,4);
return my_object;
}
[...]
Mais par contre, pas de destruction propre possible avec ça...
Je ne vois pas en quoi cela enpèche la destruction de l'objet -
avec un smart_pointer par exemple:
MyObject* globale_ressource()
{
static boost::scoped_ptr<MyObject>
my_object(new MyObjext(1,2,3,4));
return my_object.get();
}
Ou si tu veux la gèrer de manière explicite en utilisant la fonction
originelle, tu défini la globale:
static boost::scoped_ptr<MyObject>
my_object_handler(globale_ressource());
Puis
my_object_handler.reset(); //Pour une destruction explicite.
Sinon, l'objet sera détruit à la destruction de my_object_handler.
[...]
Nécessaire, c'et beaucoup dire. L'OS s'en occupe tout seul
en général.
Les seuls objets qui posent éventuellement problème sont
ceux qui spawn des threads mais ceux là sont en général
explicitement gérés (ie. prévenus de la fermeture du framework).
Certes, mais si on compile en mode debug, on se prend des tas
d'injures disant que la bibliothèque ne sait pas restituer proprement
la mémoire, et ça gâche la qualité du produit.
Au minimum ça peut conduire à traquer des bugs qui sont en fait
"volontaires", ce qui est toujours désagréable.Pour les cas où cela serait vraiment nécessaire, par exemple
pour rendre une ressource non géré par le système, tu pourrais
utiliser un smart pointer dans la static.
C'est ce que je voulais dire en parlant de lock-count.
Bon là on perd le bénéfice du test évité dans l'accesseur...
De toutes façon, en environement multithreadé, le lock count ne te
servira à rien. Il suffit d'un seul tread qui se termine de façon
anormale (ie. sans dépiler sa pile si tu utilise RAII) pour le compte
soit faussé.
Peut être le plus simple est d'utiliser un smart_pointer dans la
fonction d'accès, si l'objet est déjà détruit, il renverra NULL
et ton destructor custom agira en conséquence.
Pour l'utilisateur, il suffit de mettre dans la doc que tu n'assure pas
l'utilisation des zones de mémoire allouées par ton objet après la
sortie de main() mais que rendre la zone mémoire (delete) est
robuste ensuite, à lui de gèrer ses destructeurs pour qu'ils
n'utilisent pas cette mémoire.