Copy constructor et surcharge operateur =

Le
Jean-Noël Mégoz
Salut !

Est-il possible/normal/recommandé/scandaleux d'appeler un constructeur comme
une fonction quelconque pour surcharger l'operateur = ?

Par exemple, sur une classe String :

String::String(const String& S) // copy constructor
{
[initialisation]
}

const String& String::operator=(const String& S)
{
if(this != &S) String(S);
return *this;
}

L'idée est de ne pas retaper le même code pour faciliter la maintenance.
De même, peut-on appeler le destructeur de sa classe pour vider une instance
?

Merci d'avance pour vos réponses.
J.No.
Vos réponses Page 1 / 2
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Vincent Lascaux
Le #728952
Salut !

Est-il possible/normal/recommandé/scandaleux d'appeler un constructeur
comme

une fonction quelconque pour surcharger l'operateur = ?


Scandaleux... je ne sais pas, mais normal, non.
Par contre, on peut faire

String:String(const String& S)
{
Init(S);
}
const String& String::operator = (const String& S)
{
if(this!= &S)
Init(S);
return *this;
}
void Init(const String& S)
{
[initisalition]
}

Le seul cas où on ne peut pas transformer bêtement ta proposition en mon
code est lorsque le constructeur est écrit comme ca :
String::String(const String& S)
: member1(...), member2(...)
{
[initialisation]
}

--
Vincent

Twxs
Le #728951
Jean-Noël Mégoz wrote:
Salut !

Est-il possible/normal/recommandé/scandaleux d'appeler un constructeur comme
une fonction quelconque pour surcharger l'operateur = ?

Par exemple, sur une classe String :

const String& String::operator=(const String& S)
{
if(this != &S) String(S);
return *this;
}

L'idée est de ne pas retaper le même code pour faciliter la maintenance.
De même, peut-on appeler le destructeur de sa classe pour vider une instance
?

pour le destructeur, oui, rien ne t'empeche de faire un delete this

mais faut etr certain de ce que l'on fait

pour le reste je ne suis pas certain que ca passe mais par peur de
represailles je ne m'avance pas sur le sujet. mais moi, a ta place, je
factoriserai le code d'initalisation dans un methode privée appelée dans
le constructeur et dans l'operateur
Twxs

Horst Kraemer
Le #728949
On Tue, 4 May 2004 23:34:24 +0200, "Jean-Noël Mégoz"

Salut !

Est-il possible/normal/recommandé/scandaleux d'appeler un constructeur comme
une fonction quelconque pour surcharger l'operateur = ?


Impossible.

Par exemple, sur une classe String :

String::String(const String& S) // copy constructor
{
[initialisation]
}

const String& String::operator=(const String& S)
{
if(this != &S) String(S);
return *this;
}


Un constructeur est une fonction membre spéciale qui n'a *pas* de nom.
Par conséquent on ne peut jamais appeler un constructeur.

String(S)

n'est pas un "appel d'un constructeur" pour l'objet *this mais une
instruction qui crée un *nouveau* objet du type String par copie - et
le jette immédiatement, c.a.d. dans String(S) 'String' ne joue pas le
rôle de nom de constructeur (qui n'existe pas), mais de "nom de
classe" - et l'appel d'un nom de classe crée toujours un nouveau
objet.

--
Horst

Jean-Noël Mégoz
Le #724382
"Vincent Lascaux" news:40980fd7$0$21081$
Salut !

Par contre, on peut faire


String:String(const String& S)
{
Init(S);
}
const String& String::operator = (const String& S)
{
if(this!= &S)
Init(S);
return *this;
}
void Init(const String& S)
{
[initisalition]
}



Je n'avais pas pensé à faire ça, c'est pourtant bête comme chou !
Merci à toi, et aux autres, pour vos réponses.

Le seul cas où on ne peut pas transformer bêtement ta proposition en mon
code est lorsque le constructeur est écrit comme ca :
String::String(const String& S)
: member1(...), member2(...)
{
[initialisation]
}

Oh, je n'en suis pas encore là ! ;)



Loïc Joly
Le #724380
Jean-Noël Mégoz wrote:

Salut !

Est-il


[ ] possible
[ ] normal
[ ] recommandé
[X] scandaleux


d'appeler un constructeur comme
une fonction quelconque pour surcharger l'operateur = ?

Par exemple, sur une classe String :

String::String(const String& S) // copy constructor
{
[initialisation]
}

const String& String::operator=(const String& S)
{
if(this != &S) String(S);
return *this;
}

L'idée est de ne pas retaper le même code pour faciliter la maintenance.


Idée louable s'il en est.
La solution "canonique" en ce moment (et qui ne pose pas de problèmes
avec les exceptions) a l'air d'être :

String::swap(string &other) // throw()
{
std::swap(myInternalBuffer, other.myInternalBuffer);
std::swap(mySize, other.mySize);
}

String::String(String const &other)
{
// Implémentation
}

String &String::operator=(string const &other)
{
String temp(other);
swap(temp);
return *this;
}

Le problème est qu'il faut quand même écrire swap et le constructeur de
copie, mais souvent swap est très simple à écrire.

--
Loïc

Jean-Noël Mégoz
Le #724379
"Jean-Noël Mégoz" news:409817fc$0$13069$

"Vincent Lascaux" news:40980fd7$0$21081$
Salut !

Par contre, on peut faire


String:String(const String& S)
{
Init(S);
}
const String& String::operator = (const String& S)
{
if(this!= &S)
Init(S);
return *this;
}
void Init(const String& S)
{
[initisalition]
}



Je n'avais pas pensé à faire ça, c'est pourtant bête comme chou !
Merci à toi, et aux autres, pour vos réponses.



À la réflexion, il faudrait plutôt écrire Init(this, S) : c'est bien
l'instance désignée par this et pas S, qu'on veut initialiser !
Ce qui ne change rien au fait que la méthode est bonne...



Vincent Lascaux
Le #724376
void Init(const String& S)
{
[initisalition]
}

À la réflexion, il faudrait plutôt écrire Init(this, S) : c'est bien


l'instance désignée par this et pas S, qu'on veut initialiser !
Ce qui ne change rien au fait que la méthode est bonne...


Ou plus logiquement
void String::Init(const String& S)
{
[Initialisation]
}

désolé pour l'erreur

--
Vincent



Jean-Marc Bourguet
Le #724108
"Jean-Noël Mégoz"
Salut !

Est-il possible/normal/recommandé/scandaleux d'appeler un constructeur comme
une fonction quelconque pour surcharger l'operateur = ?

Par exemple, sur une classe String :

String::String(const String& S) // copy constructor
{
[initialisation]
}

const String& String::operator=(const String& S)
{
if(this != &S) String(S);
return *this;
}

L'idée est de ne pas retaper le même code pour faciliter la
maintenance. De même, peut-on appeler le destructeur de sa classe
pour vider une instance ?


String(S) n'est pas un appel au constructeur de copie mais la creation
d'une variable nommee S initialisee par le constructeur par defaut (et
non un temporaire initialise par copie comme le pensait Horst).

On peut faire ce que tu veux:

if (this != &S) {
this->~String();
new (this) String(S);
}

mais ca pose un certain nombre de problemes dont le plus connu est le
risque cause par un constructeur de copie qui jette des exceptions.

Comme l'ecrit Loic, La maniere systematique la plus preconnisee pour
le moment d'implementer l'affectation est de creer une copie puis de
swapper les elements (pas avec std::swap sauf s'il est specialise pour
ne pas utiliser l'affectation !) mais cela n'est pas toujours la
meilleure maniere.

A propos du test (this != &S), il a ete dit que s'il est necessaire,
le code de l'assignation est incorrect en presence d'exceptions.

(Au fait, il vaut mieux retourner String& plutot que String const& en
resultat de l'affectation, c'est le type retourne par la version
generee par le compilateur et les raisons qui font preferer retourner
un const ne sont generalement pas suffisantes pour ne pas faire de
meme.)

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

kanze
Le #724102
Loïc Joly news:
Jean-Noël Mégoz wrote:

Est-il


[ ] possible
[ ] normal
[ ] recommandé
[X] scandaleux


Ça dépend. Si c'est ce qu'on veut, pourquoi pas ? Seulement, quand on
analyse les cas réelement, on constate que ce n'est jamais ce qu'on
veut.

d'appeler un constructeur comme une fonction quelconque pour
surcharger l'operateur = ?

Par exemple, sur une classe String :

String::String(const String& S) // copy constructor
{
[initialisation]
}

const String& String::operator=(const String& S)
{
if(this != &S) String(S);



Ce qui créer un temporaire de type String, puis le jette.

Pour invoquer un constructeur sur un objet donné, il faut se servir du
syntax de new de placement :

new ( this ) String( S ) ;

Mais comme j'ai dit ci-dessus, ce n'est prèsque jamais ce qu'on veut.

Aussi, pour une classe nommée String, il est fort probable qu'il
faudrait un peu de nettoyage d'abord. L'idiome « classique » (mais c'est
un anti-idiome) serait donc :

if ( this != &S ) {
this->~String() ;
new ( this ) String( S ) ;
}

C'est un anti-idiome à cause des problèmes qu'il pose, voir
http://www.gotw.ca/gotw/023.htm. L'abstraction, tout au début, dit le
tout : « [it's] often dangerously wrong ».

return *this;
}

L'idée est de ne pas retaper le même code pour faciliter la
maintenance.


Idée louable s'il en est.

La solution "canonique" en ce moment (et qui ne pose pas de problèmes
avec les exceptions) a l'air d'être :


C'est une solution à la mode. AMHA, elle n'est réelement intéressante
que si on comprend pourquoi elle marche.

String::swap(string &other) // throw()
{
std::swap(myInternalBuffer, other.myInternalBuffer);
std::swap(mySize, other.mySize);
}

String::String(String const &other)
{
// Implémentation
}

String &String::operator=(string const &other)
{
String temp(other);
swap(temp);
return *this;
}

Le problème est qu'il faut quand même écrire swap et le constructeur
de copie, mais souvent swap est très simple à écrire.


Swap est simple dans le cas où la classe consiste surtout en pointeurs à
des données allouées dynamiquement, et peut-être quelque types de base
en plus. Ou en d´éléments qui eux aussi implémente une version de swap
qui le lève pas d'exception (comme par exemple std::vector).

Si la classe ne contient que des types de base, ou des types avec des
constructeurs de copie ayant la sémantique ce ceux généré par le
compilateur (comme par exemple std::complex), c'est beaucoup plus de
travail pour rien. Si par « copie », tu peux te contenter d'une copie
supérficielle de chaqu'un des éléments, et que ces copies ne peut pas
lever d'exception, l'algorithme classique évident s'impose :

Classe&
Classe::operator=( Classe const& other )
{
a = other.a ;
b = other.b ;
// ...
return *this ;
}

Si la classe utilise l'idiome du pare-feu de compilation, l'idiome est
bien, mais je ne trouve pas de raison pour le préférer au plus simple :

Classe&
Classe::operator=( Classe const& other )
{
Impl* tmp = new Impl( *other.myImpl ) ;
// ou : other.myImpl->clone(), selon le cas...
delete myImpl ;
myImpl = tmp ;
return *this ;
}

AMHA, l'idiome de swap ne commence à être intéressant que lorsque 1) on
a plusieurs pointeurs, ou 2) on a des membres qui supporte un swap sans
exceptions, mais dont la copie pourrait lever une exception (par
exemple, std::vector -- mais aussi probablement String, ci-dessus, ou
des classes qui utilise l'idiome du pare-feu de compilation). Pour qu'il
soit réelement intéressant dans ce deuxième cas, en revanche, il faut
que les classes plus simple le tient en compte, en offrant une fonction
swap qui convient, même si elles ne s'en servent pas dans l'affectation
elles-même. (Donc, par exemple, tout en écrivant l'opérateur
d'affectation d'une classe à pare-feu de compilation comme ci-dessus, je
lui donnerait bien une fonction membre swap, qui échange les deux
pointeurs.)

Si ta classe contient des membres complexes qui ne supporte pas un swap
sans risque d'exception, c'est plutôt un anti-idiome. L'implémentation
générique de std::swap appelle le constructeur de copie et l'opérateur
d'affectation des opérands.

Et pour revenir au départ : la raison pour cet idiome (et mon idiome
pour des classes à pare-feu de compilation), c'est qu'il faut avoir fait
toute opération qui peut lever une exception (et donc, toute allocation
de nouvelle mémoire) *avant* de commencer la moindre modification dans
l'objet cible de l'affectation. Si j'écris le pare-feu de compilation :

Classe&
Classe::operator=( Classe const& other )
{
delete myImpl ;
myImpl = new Impl( *other.myImpl ) ;
return *this ;
}

que se passe-t-il si le new lève une exception ? Dans quel état se
trouve l'objet ? Et qu'est-ce qui va se passer quand on appelle le
destructeur sur l'objet ? (Dans certains cas, mettre myImpl à NULL entre
le delete et le new pourrait suffir. Mais faire le new *avant* le delete
marche dans tous les cas.)

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Jean-Noël Mégoz
Le #728688
"Jean-Noël Mégoz" news:40980acc$0$13086$
Salut !

Est-il possible/normal/recommandé/scandaleux d'appeler un constructeur
comme

une fonction quelconque pour surcharger l'operateur = ?



(je relance un branche dans l'arbre de cette conversation, car elle découle
de tout ce que vous avez dit avant).

Petit question théorico-philosophique : puisqu'on ne peut pas *appeler* un
constructeur, pourquoi ceux-ci (comme le destructeur, d'ailleur) sont-ils
"public" et non "private" dans les classes ?

Publicité
Poster une réponse
Anonyme