OVH Cloud OVH Cloud

Post-constructeur

17 réponses
Avatar
amerio
Bonjour,
Je voudrais *garantir* l'appel à une fonction virtuelle juste après la construction d'un
objet dérivé.
Ex :
class A
{
public :
A() {}
virtual Polish() { cout << "ptite couche de vernis" << endl;}
}

class B : public A
{
public :
B() {}
virtual Polish() { cout << "finalement, de la peinture plutot" << endl;}
}

Et je voudrais :
int main()
{
A a; // je voudrais appeller A::Polish
B b; // je voudrais appeller B::Polish (et pas A::Polish)
}

Pour l'instant, je suis obligé de faire ainsi :
Je déclare mes constructeurs protected, j'ajoute une fonction :
dans A :
static A* NewA() {A* pA=new A; pA->Polish(); return A;}
dans B :
static B* NewB() {B* pB=new B; pB->Polish(); return B;}
Et je construit mes objets via NewA ou NewB

En faisant ainsi, je suis obligé de rajouter une méthode NewX à chaque niveau d'héritage,
ce qui est lourd.
Et comme bien sûr "virtual static" n'existe pas...

Avez vous une meilleure idée ?

10 réponses

1 2
Avatar
James Kanze
"amerio" writes:

|> Je voudrais *garantir* l'appel à une fonction virtuelle juste
|> après la construction d'un objet dérivé.

|> Ex :
|> class A
|> {
|> public :
|> A() {}
|> virtual Polish() { cout << "ptite couche de vernis" << endl;}
|> }

|> class B : public A
|> {
|> public :
|> B() {}
|> virtual Polish() { cout << "finalement, de la peinture plutot" << endl;}
|> }

|> Et je voudrais :
|> int main()
|> {
|> A a; // je voudrais appeller A::Polish
|> B b; // je voudrais appeller B::Polish (et pas A::Polish)
|> }

|> Pour l'instant, je suis obligé de faire ainsi :
|> Je déclare mes constructeurs protected, j'ajoute une fonction :
|> dans A :
|> static A* NewA() {A* pA=new A; pA->Polish(); return A;}
|> dans B :
|> static B* NewB() {B* pB=new B; pB->Polish(); return B;}
|> Et je construit mes objets via NewA ou NewB

|> En faisant ainsi, je suis obligé de rajouter une méthode NewX
|> à chaque niveau d'héritage, ce qui est lourd.

|> Et comme bien sûr "virtual static" n'existe pas...

|> Avez vous une meilleure idée ?

Ça peut poser de problèmes si on utilise A ou B comme des
temporaires, mais dans la passée, je me suis servi des objets
spéciaux comme paramètres du constructeur, par exemple :

class FinishA ;

class A
{
public:
A( FinishA const& = FinishA() ) {}
// ...
} ;

class FinishA
{
public:
friend class A ;
FinishA() : owner( NULL ) {}
~FinishA<) { if ( owner != NULL ) owner->Polish() ; }
private:
A* owner ;
} ;

A::A( FinishA const& helper )
{
const_cast< FinishA& >( helper ).owner = this ;
}

Le problème, évidemment, c'est dans des expressions du genre :
« A().uneFonction() », où A::uneFonction serait appelée
avant Polish.

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France +33 1 41 89 80 93
Avatar
Vincent Richard

Pour l'instant, je suis obligé de faire ainsi :
Je déclare mes constructeurs protected, j'ajoute une fonction :
dans A :
static A* NewA() {A* pA=new A; pA->Polish(); return A;}
dans B :
static B* NewB() {B* pB=new B; pB->Polish(); return B;}
Et je construit mes objets via NewA ou NewB

En faisant ainsi, je suis obligé de rajouter une méthode NewX à chaque
niveau d'héritage, ce qui est lourd.
Et comme bien sûr "virtual static" n'existe pas...

Avez vous une meilleure idée ?


Déjà, on peut simplifier en passant par un objet intermédiaire pour
créer des objets de type A :

class createurObjet
{
public:

template <class TYPE>
static TYPE* nouveau()
{
A* obj = new TYPE;
obj->Polish();
return static_cast<TYPE*>(obj);
}
};

et pour l'appel :

A* a = createurObjet::nouveau <A>();
B* b = createurObjet::nouveau <B>();

Vincent

--
SL> Au fait elle est mieux ma signature maintenant ?
Oui. T'enlève encore les conneries que t'as écrit dedans et c'est bon.
-+- JB in <http://www.le-gnu.net> : Le neuneuttoyage par le vide -+-

Avatar
amerio
Pour l'instant, je suis obligé de faire ainsi :
Je déclare mes constructeurs protected, j'ajoute une fonction :
dans A :
static A* NewA() {A* pA=new A; pA->Polish(); return A;}
dans B :
static B* NewB() {B* pB=new B; pB->Polish(); return B;}
Et je construit mes objets via NewA ou NewB

En faisant ainsi, je suis obligé de rajouter une méthode NewX à chaque
niveau d'héritage, ce qui est lourd.
Et comme bien sûr "virtual static" n'existe pas...

Avez vous une meilleure idée ?


Déjà, on peut simplifier en passant par un objet intermédiaire pour
créer des objets de type A :

class createurObjet
{
public:

template <class TYPE>
static TYPE* nouveau()
{
A* obj = new TYPE;
obj->Polish();
return static_cast<TYPE*>(obj);
}
};

et pour l'appel :

A* a = createurObjet::nouveau <A>();
B* b = createurObjet::nouveau <B>();



Oui, en fait, c'est déjà ce que je fait : j'utilise une "Fabrique".
Mais l'idée reste la même : déléguer la construction à un tier de confiance.
Le problème est qu'un dévelopeur écrivant une nouvelle fabrique peut parfaitement oublier
l'appel au Polish. Ce que je veux éviter, en détectant cet oubli à la compilation (!).


Avatar
amerio
Pour l'instant, je suis obligé de faire ainsi :
Je déclare mes constructeurs protected, j'ajoute une fonction :
dans A :
static A* NewA() {A* pA=new A; pA->Polish(); return A;}
dans B :
static B* NewB() {B* pB=new B; pB->Polish(); return B;}
Et je construit mes objets via NewA ou NewB

En faisant ainsi, je suis obligé de rajouter une méthode NewX
à chaque niveau d'héritage, ce qui est lourd.

Et comme bien sûr "virtual static" n'existe pas...

Avez vous une meilleure idée ?



Ça peut poser de problèmes si on utilise A ou B comme des
temporaires, mais dans la passée, je me suis servi des objets
spéciaux comme paramètres du constructeur, par exemple :

class FinishA ;

class A
{
public:
A( FinishA const& = FinishA() ) {}
// ...
} ;

class FinishA
{
public:
friend class A ;
FinishA() : owner( NULL ) {}
~FinishA<) { if ( owner != NULL ) owner->Polish() ; }
private:
A* owner ;
} ;

A::A( FinishA const& helper )
{
const_cast< FinishA& >( helper ).owner = this ;
}

Le problème, évidemment, c'est dans des expressions du genre :
« A().uneFonction() », où A::uneFonction serait appelée
avant Polish.


Dans mon cas précis, je suis sûr de n'avoir pas de temporaire (à cause de la nature de mes
objets, et ils sont boost::noncopyable en plus)
L'idée est séduisante (j'adore le principe RAII).
Le Helper est construit avant l'appel au constructeur de A (puisque c'est un paramètre),
puis est détruit juste après..
Oui, je crois que j'adopte l'idée ! :-) Merci !



Avatar
Fabien LE LEZ
On Sat, 27 Sep 2003 17:37:09 GMT, "amerio" wrote:

Oui, en fait, c'est déjà ce que je fait : j'utilise une "Fabrique".


Mais là, la fabrique est un template, donc nul besoin de la refaire à
chaque nouvelle classe.

Avatar
amerio
Fabien LE LEZ wrote:
On Sat, 27 Sep 2003 17:37:09 GMT, "amerio" wrote:

Oui, en fait, c'est déjà ce que je fait : j'utilise une "Fabrique".


Mais là, la fabrique est un template, donc nul besoin de la refaire à
chaque nouvelle classe.


Oops ! Exact, j'avais lu trop vite, reconnaisant une fabrique !
Effectivement, alors, une Fabrique Template resout mon pb.
Du coup, j'ai deux solutions : soit un Helper parametrisant le constructeur, soit une
Fabrique Template.
(mais les ecritures ne sont pas du tout les mêmes).
Je vais voir ce qui me convient le mieux !
Merci !


Avatar
Christophe Brun
Le 27 Sep 2003 18:26:13 +0200, James Kanze a
écrit:

"amerio" writes:

|> Je voudrais *garantir* l'appel à une fonction virtuelle juste
|> après la construction d'un objet dérivé.

[...]
|> Et je voudrais :
|> int main()
|> {
|> A a; // je voudrais appeller A::Polish
|> B b; // je voudrais appeller B::Polish (et pas A::Polish)
|> }

[ J.Kanze ] :
class FinishA ;

class A
{
public:
A( FinishA const& = FinishA() ) {}
// ...
} ;

class FinishA
{
public:
friend class A ;
FinishA() : owner( NULL ) {}
~FinishA<) { if ( owner != NULL ) owner->Polish() ; }
private:
A* owner ;
} ;

A::A( FinishA const& helper )
{
const_cast< FinishA& >( helper ).owner = this ;
}


La solution est intéressante pour une classe unique, mais il me semble
qu'elle ne répond pas aux spécifications d'amerio dans le cas d'une
hiérarchie de classes. Dans ce cas en effet, il va falloir non seulement
implémenter chaque constructeur de la famille A, mais surtout ajouter dans
la classe FinishA une clause friend pour chaque nouvelle classe de cette
famille, ce qui risque de devenir envahissant (et fragile).

Plus grave, dans le cas suivant :

class A { ...};
class B : public A { ... };
int main()
{
B b;
return 0;
}

...la méthode B::Polish est appellée deux fois : une première fois depuis
A::A() (premier constructeur appelé puisque A est la classe de base), une
deuxième fois depuis B::B().

A première vue, je ne vois pas de solution non intrusive à ce type de
problème. Pour garantir l'appel à Polish() lors de la construction de
l'objet de la famille A, il faut passer par une encapsulation du
constructeur de A et de ses descendants, donc passer les constructeurs en
protected. Or, il est impossible d'instancier un objet dont le constructeur
n'est pas public (à moins peut être de tripoter l'operator new, mais
j'avoue avoir toujours évité de me lancer dans ce genre de
manipulations...). Pour garantir l'appel à la bonne version de Polish() et
pour garantir l'unicité de cet appel quand on utilise une classe dérivée,
on doit placer l'appel à Polish() en dehors des constructeurs des objets de
la famille A.

En première analyse, je proposerai quelque chose du style, inspiré du Petit
Patron Têtu de Coplien (Curiously Recurring Template Pattern) :

class A
{
public :
virtual void Polish() { cout << "A::Polish" << endl; }

protected :
A() {}
};

class B : public A
{
public :
virtual void Polish() { cout << "B::Polish" << endl; }

protected :
B() {}
};

template< typename T >
class newA : public T
{
public :

newA()
{ getObj().Polish(); }

T& getObj() // pour revenir à des objets de type A, B, etc... en dehors
de leur construction
{ return static_cast< T& >( *this ); }

const T& getObj() const // id...
{ return static_cast< const T& >( *this ); }
};

int main()
{
newA< A > a;
newA< B > b;
return 0;
}

Evidement, cette approche oblige à modifier le code existant...

--
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/

Avatar
amerio
En première analyse, je proposerai quelque chose du style, inspiré du Petit
Patron Têtu de Coplien (Curiously Recurring Template Pattern) :

class A
{
public :
virtual void Polish() { cout << "A::Polish" << endl; }

protected :
A() {}
};

class B : public A
{
public :
virtual void Polish() { cout << "B::Polish" << endl; }

protected :
B() {}
};

template< typename T >
class newA : public T
{
public :

newA()
{ getObj().Polish(); }

T& getObj() // pour revenir à des objets de type A, B, etc... en dehors
de leur construction
{ return static_cast< T& >( *this ); }

const T& getObj() const // id...
{ return static_cast< const T& >( *this ); }
};

int main()
{
newA< A > a;
newA< B > b;
return 0;
}

Evidement, cette approche oblige à modifier le code existant...


Décidemment, tous les jours une nouvelle application des templates ! :-)
Dans mon cas, changer l'ecriture n'est pas un (trop gros) problème.
Merci d'avoir mis le doigt sur le pb de la solution précédente (j'étais en train de la
tester quand cette réponse est arrivée)
En tout cas, cette approche marche :-)

Avatar
amerio
Oui, en fait, c'est déjà ce que je fait : j'utilise une "Fabrique".


Mais là, la fabrique est un template, donc nul besoin de la refaire à
chaque nouvelle classe.


Oops ! Exact, j'avais lu trop vite, reconnaisant une fabrique !
Effectivement, alors, une Fabrique Template resout mon pb.
Du coup, j'ai deux solutions : soit un Helper parametrisant le constructeur,
soit une Fabrique Template.
(mais les ecritures ne sont pas du tout les mêmes).
Je vais voir ce qui me convient le mieux !
Merci !


Apres essais, je n'arrive pas à reproduire mon schéma actuel.
Ma fabrique actuelle est un "virtual constructor".
class Kit
{
public :
virtual Entity* NewEntity() const { Entity* pE = new Entity; pE->Polish(); return
pE;}
}

class KitA : public Kit // A dérive publiquement de Entity
{
public :
virtual Entity* NewEntity() const { Entity* pE = new A; pE->Polish(); return pE;}
}

idem pour KitB, avec B dérivant publiquement de A

Et je stocke tous ces Kit dans un map<string, Kit*>, de sorte que je fais
A* pA = mykits["A"]->NewEntity(); // A(); A::Polish()
A* pB = mykits["B"]->NewEntity(); // B(); B::Polish()

Avec un template, je n'arrive plus à le faire.



Avatar
Vincent Richard

Apres essais, je n'arrive pas à reproduire mon schéma actuel.
Ma fabrique actuelle est un "virtual constructor".
class Kit
{
public :
virtual Entity* NewEntity() const { Entity* pE = new Entity;
pE->Polish(); return
pE;}
}

class KitA : public Kit // A dérive publiquement de Entity
{
public :
virtual Entity* NewEntity() const { Entity* pE = new A;
pE->Polish(); return pE;}
}

idem pour KitB, avec B dérivant publiquement de A

Et je stocke tous ces Kit dans un map<string, Kit*>, de sorte que je fais
A* pA = mykits["A"]->NewEntity(); // A(); A::Polish()
A* pB = mykits["B"]->NewEntity(); // B(); B::Polish()


Voilà une solution avec template :

class Kit
{
private:

typedef A* (*CreateFunc)(void);
typedef std::map <std::string, CreateFunc> NameMap;

NameMap m_map;

template <class TYPE>
class registerer
{
public:

static A* creator()
{
A* obj = new TYPE;
obj->Polish();
return obj;
}
};

public:

template <class TYPE>
void registerKit(const std::string& name)
{
m_map.insert(NameMap::value_type(name, &registerer<TYPE>::creator));
}

A* create(const std::string& name) const
{
NameMap::const_iterator pos = m_map.find(name);

if (pos != m_map.end())
return ((*pos).second)();
else
return NULL;
}
};


Et pour l'utiliser :

Kit monKit;

// Enregistrement des types supportés
monKit.registerKit <A>("A");
monKit.registerKit <B>("B");

// ...

A* a = monKit.create("A");
B* b = monKit.create("B");

Vincent

--
SL> Au fait elle est mieux ma signature maintenant ?
Oui. T'enlève encore les conneries que t'as écrit dedans et c'est bon.
-+- JB in <http://www.le-gnu.net> : Le neuneuttoyage par le vide -+-

1 2