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 ?

7 réponses

1 2
Avatar
James Kanze
Christophe Brun writes:

|> 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).

Pas du tout. Il faut bien implémenter un constructeur pour chaque
classe, mais en ce qui concerne l'idiome, ce n'est que pour passer le
paramètre, c-à-d :

Derived::Derived( FinishA const& a )
: Base( a )
{
}

Et dans le cas où il l'oublie, le compilateur va râler, parce
qu'il ne doit pas avoir de constructeur par défaut pour Base.

|> 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().

D'où est-ce que tu vois ça ? Polish est appelé depuis le
destructeur du temporaire. Alors, vue qu'il n'y a qu'une instance du
temporaire, il n'est appelé qu'une seule fois.

(En passant, je me suis servi du méthode pendant longtemps dans mes
classes FieldArray.)

--
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
Christophe Brun
Le 28 Sep 2003 15:30:58 +0200, James Kanze a
écrit:


Pas du tout. Il faut bien implémenter un constructeur pour chaque
classe, mais en ce qui concerne l'idiome, ce n'est que pour passer le
paramètre, c-à-d :

Derived::Derived( FinishA const& a )
: Base( a )
{
}

Et dans le cas où il l'oublie, le compilateur va râler, parce
qu'il ne doit pas avoir de constructeur par défaut pour Base.



Ok, autant pour moi ! Sur le test que j'avais (un peu hâtivement) fait
d'après ton code, j'avais négligé de repasser le FinishA vers le
constructeur de la classe de base (d'où l'objection qui suivait concernant
l'appel
en double) :

|> 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().

D'où est-ce que tu vois ça ? Polish est appelé depuis le
destructeur du temporaire. Alors, vue qu'il n'y a qu'une instance du
temporaire, il n'est appelé qu'une seule fois.

(En passant, je me suis servi du méthode pendant longtemps dans mes
classes FieldArray.)



Je retire dons mes objections ! :o)

--
Cordialement;
Christophe Brun

Avatar
amerio
Voilà une solution avec template :
[snip]


Parfait ! Ca marche ! Mais il reste un petit soucis. En effet, pour etre sur que mon
Polish() est bien appelé, il faut que je passe obligatoirement par le Kit, donc mettre le
constructeur en protected, et le Kit en friend.
Et là, ca coince. Bon, déjà, j'ai sorti la classe registerer de Kit (pour simplifier le
test).
Si A::A() passe en protected, je dois alors ajouter dans A :
friend class registerer<A>
Jusque là, tout va bien.
Mais si je met Polish() en protected (ce qu'il *doit* etre!) et que je rajoute une classe
B:
class A
{
public :
friend class registerer<A>;
protected:
A() { cout << "A::A" << endl; }
virtual void Polish() { cout << "A::Polish" << endl; }
};
class B : public class A
{
public :
friend class registerer<B>;
protected:
B() { cout << "B::B" << endl; }
virtual void Polish() { cout << "B::Polish" << endl; }
};

Et bien alors là, ca coince.
A la compilation, VC6 refuse monKit.registerKit <B>("B");

error C2248: 'Polish' : cannot access protected member declared in class 'A'

[ Note : VC6 n'aime pas registerKit<X>("X") alors j'ai modifier ca en passant un argument
bidon de type X* pour résoudre le template ]

Et là je ne comprend pas ! Je me doute d'un pb de compilo, mais qd même !

Alors peut être faut il déclarer tous les template possible friend ?
Mais la syntaxe :

template <class T> friend class registerer<T>;

ne marche pas (error C2059: syntax error : '<end Parse>')

Pourtant, je croyais que c'etait la bonne syntaxe (enfin, c'est google qui le dis ;-) )

Un dernier coup de pouce ?

Avatar
Vincent Richard

Mais si je met Polish() en protected (ce qu'il *doit* etre!) et que je
rajoute une classe B:

[...]

Et bien alors là, ca coince.
A la compilation, VC6 refuse monKit.registerKit <B>("B");

error C2248: 'Polish' : cannot access protected member declared in class
'A'


Remplace :

obj->Polish();

par :

static_cast<TYPE*>(obj)->Polish();

dans la fonction creator().

Ca devrait fonctionner. Chez moi (g++ 3.2.2), ça compile et s'exécute
correctement après la modification.

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
Vincent Richard

Remplace :

obj->Polish();

par :

static_cast<TYPE*>(obj)->Polish();

dans la fonction creator().


Ou encore plus simplement (et plus lisible) :

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

Ca vérifie quand même que TYPE dérive bien de A (grâce à la conversion
implicite au "return").

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
Mais si je met Polish() en protected (ce qu'il *doit* etre!) et que je
rajoute une classe B:

[...]

Et bien alors là, ca coince.
A la compilation, VC6 refuse monKit.registerKit <B>("B");

error C2248: 'Polish' : cannot access protected member declared in class
'A'


Remplace :

obj->Polish();

par :

static_cast<TYPE*>(obj)->Polish();

dans la fonction creator().

Ca devrait fonctionner. Chez moi (g++ 3.2.2), ça compile et s'exécute
correctement après la modification.


Exact ! et encore merci :-)

N'empeche, je comprend pas pourquoi il a besoin du cast.
(j'aime pas copypaster sans comprendre)
registerer<A> est ami de A (donc peut acceder a A::Polish)
registerer<B> est ami de B (donc peut acceder a B::Polish)
Mais visiblement le compilo semble vouloir que registerer<B> puisse acceder a A::Polish
aussi ?!?
Le cast force le compilo a voir un 'B' et plus un 'A'. Mais pourquoi en a t on besoin ?


Avatar
Vincent Richard

N'empeche, je comprend pas pourquoi il a besoin du cast.
(j'aime pas copypaster sans comprendre)
registerer<A> est ami de A (donc peut acceder a A::Polish)
registerer<B> est ami de B (donc peut acceder a B::Polish)
Mais visiblement le compilo semble vouloir que registerer<B> puisse
acceder a A::Polish aussi ?!?


C'est bien ce qu'on fait, non ?

A* obj = new B;
obj->Polish();

On appelle bien Polish() sur un objet de type A, mais la virtualité
fait que ça sera B::Polish qui sera appelée (mais le compilateur ne
le sait pas, pour lui, c'est A::Polish qui semble appelée).

Mais cf. mon autre post de 18h31 pour éviter le static_cast<>.

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