OVH Cloud OVH Cloud

Fabrique et enregistrement automatique

5 réponses
Avatar
plotark
Bonjour a tous,

J'ai un petit probleme de conception et j'espere que quelqu'un aura quelques
idees à proposer.
J'ai une fabrique qui sert a creer des instances de classes qui sont toutes
derivées de la meme classe de base.

Ainsi ma classe ressemble a ce qui suit:

class Fabrique
{
typedef Base* (CREATE_FUNC)();

std::map<std::string,CREATE_FUNC> creators;

....
public:
Base* createObject(std::string objectName)
};

où createObject va utiliser la fonction enregistrée dans la map pour creer
l'objet.
J'aimerais que les classes, derivant de Object, que je declare s'enregistre
automatiquement dans la fabrique.

Le moyen que je pense utiliser est de faire en sorte que la fabrique soit un
singleton et dans le .cpp d'avoir un code du genre:

/// ObjectN.h
class ObjectN : public Object
{
};

/// ObjectN.cpp
#include "Fabrique.h"
#include "ObjectN.h"

Base* Create() { return static_cast<Base*>(new ObjectN); }
int ObjectN::d = Fabrique::getInstance()->register("ObjectN",Create); (d est
static dans ObjectN)

Mais cette solution ne me satisfait pas vraiment, surtout parce que je ne
suis pas certain que 'register' sera appeler effectivement par tous les
compilateurs (a cause d'optimisation puisque je n'utilise plus d apres).

Quelqu'un a t il une solution elegante a mon probleme?

Merci par avance

Plotark

5 réponses

Avatar
Arnaud Debaene
plotark wrote:
Bonjour a tous,

J'ai un petit probleme de conception et j'espere que quelqu'un aura
quelques idees à proposer.
J'ai une fabrique qui sert a creer des instances de classes qui sont
toutes derivées de la meme classe de base.

Ainsi ma classe ressemble a ce qui suit:

class Fabrique
{
typedef Base* (CREATE_FUNC)();

std::map<std::string,CREATE_FUNC> creators;

....
public:
Base* createObject(std::string objectName)
};

où createObject va utiliser la fonction enregistrée dans la map pour
creer l'objet.
J'aimerais que les classes, derivant de Object, que je declare
s'enregistre automatiquement dans la fabrique.

Le moyen que je pense utiliser est de faire en sorte que la fabrique
soit un singleton et dans le .cpp d'avoir un code du genre:

/// ObjectN.h
class ObjectN : public Object
{
};

/// ObjectN.cpp
#include "Fabrique.h"
#include "ObjectN.h"

Base* Create() { return static_cast<Base*>(new ObjectN); }
int ObjectN::d = Fabrique::getInstance()->register("ObjectN",Create);
(d est static dans ObjectN)

Mais cette solution ne me satisfait pas vraiment, surtout parce que
je ne suis pas certain que 'register' sera appeler effectivement par
tous les compilateurs (a cause d'optimisation puisque je n'utilise
plus d apres).


A quoi de sert ObjectN::d exactement, et de quel type est-il? register ne
devrait rien renvoyer et lever une exception en cas d'erreur (de toute
façon, si tes classes dérivées ne peuvent s'enregistrer dans la factory, le
mieux est d'aborter immédiatement ton programme).

A part çà, c'est la méthode classique de faire une factory. Je ne pense pas
qu'un compilateur est le droit de zapper l'appel à register.

Arnaud

Avatar
Loïc Joly
Arnaud Debaene wrote:
/// ObjectN.cpp
#include "Fabrique.h"
#include "ObjectN.h"

Base* Create() { return static_cast<Base*>(new ObjectN); }
int ObjectN::d = Fabrique::getInstance()->register("ObjectN",Create);
(d est static dans ObjectN)

Mais cette solution ne me satisfait pas vraiment, surtout parce que
je ne suis pas certain que 'register' sera appeler effectivement par
tous les compilateurs (a cause d'optimisation puisque je n'utilise
plus d apres).



A part çà, c'est la méthode classique de faire une factory. Je ne pense pas
qu'un compilateur est le droit de zapper l'appel à register.


Certains le font si le code en question est dans une bibliothèque
séparée. Il est alors possible de lui dire d'inclure quand même cet
objet avec une option du linker faite pour.

--
Loïc


Avatar
kanze
"Arnaud Debaene" wrote in message
news:<3ffdb2b8$0$28714$...
plotark wrote:

J'ai un petit probleme de conception et j'espere que quelqu'un aura
quelques idees à proposer. J'ai une fabrique qui sert a creer des
instances de classes qui sont toutes derivées de la meme classe de
base.

Ainsi ma classe ressemble a ce qui suit:

class Fabrique
{
typedef Base* (CREATE_FUNC)();

std::map<std::string,CREATE_FUNC> creators;

....
public:
Base* createObject(std::string objectName)
};

où createObject va utiliser la fonction enregistrée dans la map pour
creer l'objet. J'aimerais que les classes, derivant de Object, que
je declare s'enregistre automatiquement dans la fabrique.

Le moyen que je pense utiliser est de faire en sorte que la fabrique
soit un singleton et dans le .cpp d'avoir un code du genre:

/// ObjectN.h
class ObjectN : public Object
{
};

/// ObjectN.cpp
#include "Fabrique.h"
#include "ObjectN.h"

Base* Create() { return static_cast<Base*>(new ObjectN); }
int ObjectN::d = Fabrique::getInstance()->register("ObjectN",Create);
(d est static dans ObjectN)

Mais cette solution ne me satisfait pas vraiment, surtout parce que
je ne suis pas certain que 'register' sera appeler effectivement par
tous les compilateurs (a cause d'optimisation puisque je n'utilise
plus d apres).


A quoi de sert ObjectN::d exactement, et de quel type est-il?


Si j'ai bien compris, qu'importe. Ce n'est pas l'ObjectN::d qui nous
intéresse, mais les effets de borde de sont initialisation.

C'est à quelques détails près la solution que j'utilise régulièrement
moi-même. Dans mon cas, à la place des pointeurs vers des fonctions,
j'utilise un pointeur vers une classe abstraite de base, qui ressemble à
peu près à :

class ObjectFactory
{
public:
ObjectFactory( std::string const& name )
{
Factory::instance().register( name, this ) ;
}
virtual ~ObjectFactory() {}
virtual Base* create() const = 0 ;
} ;

Ensuite, chaque type dérivé contient un membre statique d'une classe
dérivée de ObjectFactory -- on peut se servir d'un template :

template< typename T >
class ConcreteObjectFactory
{
public:
ConcreteObjectFactory( std::string const& name )
: ObjectFactory( name )
{
}

virtual Base* create() const
{
return new T() ;
}
}

À la construction de l'objet statique, la régistration se fait
automatiquement.

register ne devrait rien renvoyer et lever une exception en cas
d'erreur (de toute façon, si tes classes dérivées ne peuvent
s'enregistrer dans la factory, le mieux est d'aborter immédiatement
ton programme).

A part çà, c'est la méthode classique de faire une factory. Je ne
pense pas qu'un compilateur est le droit de zapper l'appel à register.


La question est subtile.

Du point de vue théorique, §3.6.2/3 dit « It is implementation-defined
whether or not the dynamic initializeion of an object of namespace scope
is done before the first statement of main. If the initialization is
deferred to some point in time after the first statement of main, it
shall occur before the first use of any function or object defined in
the same translation unit as the object to be initialized. » Ce qui pose
un petit problème ici, parce que tant que l'initialisation n'a pas eu
lieu, on n'utilise aucune fonction ni aucun objet de l'unité de
traduction.

Dans la pratique, ceci n'est pas un problème, parce que toutes les
implémentations effectuent les initialisations dynamiques avant main, À
CONDITION que le fichier objet est linké statiquement. (Attention aux
DLL ou .so.)

Dans la pratique, en revanche, il se pose un problème de comment
spécifier si une partie du code fait partie du programme ou non. Sur
cette question, la norme est silente -- comment on spécifie les sources
à compiler et les objets déjà compilés à incorporer dans le programme
est défini uniquement par l'implémentation. Et c'est là que beaucoup de
monde se trompe -- historiquement, depuis que je m'en souviens, et
aujourd'hui, avec tous les systèmes que je connais, un objet dans une
bibliothèque ne fait partie du programme que s'il résoud un symbole qui
serait autrement indéfini. Si on met le fichier objet avec le code
ci-dessus dans une bibliothèque, et on linke contre la bibliothèque,
sans autres précautions, cet objet ne ferait pas partie du programme.
(Et évidemment, les objets ne faisant pas partie du programme ne sont
pas initialisé.) En général, il existe des commandes pour dire au linker
de l'incorporer quand même, mais ces commandes varient énormement d'un
système à l'autre.

En gros, si tu spécifies les fichiers objets explicitement, et que tu
linkes tout statiquement, la technique fonctionne. Sinon, il faut faire
attention.

Une technique qui m'a servie parfois dans la passée, c'est de rendre les
instances de fabrique publique, puis d'écrire une source C++ qui en
contient leurs adresses dans un tableau :

ObjectFactory* iniTable[] {
&Type1::factory,
&Type2::factory,
// ...
} ;

Je linke ce fichier explicitement avant l'inclusion des bibliothèques,
pour générer les externes non résolues qui provoquent l'inclusion de
l'objet dans l'application.

Pour ceux qui se plain que ça veut dire que je dois maintenir une liste
quelque part avec tous les types, tout ce que je peux dire, c'est que
cette liste, il faut que je le maintient d'une façon ou d'une autre. Que
les noms de types apparaissent explicitement, dans une source C++ dédiée
qui ne contient que ça, ou qu'ils apparaissent implicitement, à travers
les noms de fichiers dans une commande de link (dans le make),
qu'importe. (En fait, avec GNU make, au moins, à condition de respecter
des conventions de nommage, c'est assez simple à générer ce fichier
automatiquement à partir d'une liste de types dérivés, du genre :

derivedTypes = Type1 Type2 ...

.)

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16


Avatar
Plotark
Bonjour,

Si j'ai bien compris, qu'importe. Ce n'est pas l'ObjectN::d qui nous
intéresse, mais les effets de borde de sont initialisation.


Oui c'est bien cela!

[...]

Dans la pratique, ceci n'est pas un problème, parce que toutes les
implémentations effectuent les initialisations dynamiques avant main, À
CONDITION que le fichier objet est linké statiquement. (Attention aux
DLL ou .so.)


[...]

En gros, si tu spécifies les fichiers objets explicitement, et que tu
linkes tout statiquement, la technique fonctionne. Sinon, il faut faire
attention.


Voila le probleme, c'est que ma fabrique est justement dans une dll! Donc le
compilo ne sait pas encore comment vont s'utiliser les fonctions que
j'enregistre dans la map.

Une technique qui m'a servie parfois dans la passée, c'est de rendre les
instances de fabrique publique, puis d'écrire une source C++ qui en
contient leurs adresses dans un tableau :


[...]

C'est a peu de chose pres ce que je fais actuellement. Mais comme il faut
maintenir cette table, je ne trouvais pas ca "elegant". Alors je me
demandais s'il existait d'autres methodes pour enregistrer automatiquement
les objets sans devoir recourir a cet genre d'"astuces" pour forcer le
compilateur a evaluer le code d'enregistrement.

[...]


Pour ceux qui se plain que ça veut dire que je dois maintenir une liste
quelque part avec tous les types, tout ce que je peux dire, c'est que
cette liste, il faut que je le maintient d'une façon ou d'une autre.


[...]

C'est exactement cette consequence qui me gene! Et je pensais aussi faire un
script pour maintenir cette liste.
Merci beaucoup il semblerait qu'il n'y ait pas d'autres solutions!

Plotark

Avatar
kanze
"Plotark" wrote in message
news:<bttmet$miq$...
[...]

Dans la pratique, ceci n'est pas un problème, parce que toutes les
implémentations effectuent les initialisations dynamiques avant
main, À CONDITION que le fichier objet est linké statiquement.
(Attention aux DLL ou .so.)


[...]

En gros, si tu spécifies les fichiers objets explicitement, et que
tu linkes tout statiquement, la technique fonctionne. Sinon, il faut
faire attention.


Voila le probleme, c'est que ma fabrique est justement dans une dll!
Donc le compilo ne sait pas encore comment vont s'utiliser les
fonctions que j'enregistre dans la map.


Et quand est-ce que la DLL est chargée ? Parce qu'une DLL, ce n'est pas
une bibliothèque, mais un fichier objet -- comme un objet, c'est du tout
ou rien. Or, la norme ne dit rien sur les DLL, mais dans la pratique, je
crois que les constructeurs des objets statiques seront appelés lors du
chargement. (C'est le cas avec des SO sous Solaris, mais j'imagine que
la pratique est assez universelle.)

Ce qui est certain, c'est que l'initialisation de la variable statique
n'aura pas lieu avant que la DLL qui le contient est chargée. En
revanche, je crois qu'il y a de fortes chances que l'initialisation a
lieu, systèmatiquement, et indépendamment des références à l'objet par
ailleurs. Quand la DLL est chargée -- si tu la charges explicitement
(LoadLibrary, ou quelque chose de ce genre, je crois), tu sais donc
quand l'initialisation aura lieu. Si tu comptes sur le chargement
implicit, il faudrait démander dans les groupes Windows comment faire
pour être sûr que le chargement a lieu.

Une technique qui m'a servie parfois dans la passée, c'est de rendre
les instances de fabrique publique, puis d'écrire une source C++ qui
en contient leurs adresses dans un tableau :


[...]

C'est a peu de chose pres ce que je fais actuellement. Mais comme il
faut maintenir cette table, je ne trouvais pas ca "elegant".


Mais il faudrait sinon maintenir la liste des objets dans le fichier
main, non. Quoique tu fasses, il faut bien quelque part que tu dises
quels objets appartiennent au programme.

Et comme j'ai dit, avec un bon système, c'est assez simple à générer le
fichier automatiquement à partir de la liste des fichiers dans le
fichier make -- dix ou quinze lignes d'AWK doit faire l'affaire. (Si ça
t'intéresse, je fais quelque chose de semblable pour mes programmes de
test -- ou le main doit référer à tous les autres fichiers. C'est un peu
plus compliqué, parce qu'il s'agit de générer du code executable à côté
de la table, mais l'idée de base est la même. C'est disponible à ma
site.)

Alors je me demandais s'il existait d'autres methodes pour enregistrer
automatiquement les objets sans devoir recourir a cet genre
d'"astuces" pour forcer le compilateur a evaluer le code
d'enregistrement.


En fait, je crois que SI les objets statiques sont dans une DLL, et SI
tu fais ce qu'il faut pour que le DLL soit bien chargée, je ne crois pas
qu'il y aurait un problème. Mais évidemment, dès qu'il y a des DLL en
jeu, on n'a pas de garantie de la norme.

[...]

Pour ceux qui se plain que ça veut dire que je dois maintenir une
liste quelque part avec tous les types, tout ce que je peux dire,
c'est que cette liste, il faut que je le maintient d'une façon ou
d'une autre.


[...]

C'est exactement cette consequence qui me gene!


Mais c'est inévitable. Make ne peut pas déviner quelles modules qui font
partie de ton programme. Au moins que tu mets toutes ces modules dans un
répertoire à part, et que ton make permet des choses comme $(patsubs
%.cc,%.obj,*.cc)

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16