OVH Cloud OVH Cloud

Constructor et vector par defaut

42 réponses
Avatar
korchkidu
Bonjour,

j'ai une classe dont les objets peuvent etre construits en specifiant ou
non le dernier parametre qui est un std::vector. J'avais donc simplement
utilise un parametre par defaut qui mettait le vector vide s'il n'etait
pas initialise. Mais dernierement, on me dit (avec des explications
fumeuses que je n'ai pas comprises...) qu'il fallait pas faire ca. Je me
retrouve donc a faire 2 constructeurs appelant une fonction init. Celui
qui n'a pas le parametre associe au vector le cree simplement (vide) et
appelle la fonction init. Je trouve ca plus que douteux comme solution.
Alors pourquoi la premiere solution n'etait pas correcte?

Merci d'avance,
K.

10 réponses

1 2 3 4 5
Avatar
James Kanze
korchkidu wrote:
Fabien LE LEZ wrote:


On Tue, 04 Jan 2005 13:29:38 +0100, korchkidu :



J'avais donc simplement utilise un parametre par defaut qui
mettait le vector vide s'il n'etait pas initialise. Mais
dernierement, on me dit (avec des explications fumeuses que
je n'ai pas comprises...) qu'il fallait pas faire ca.




Ah ? Je veux bien voir les explications en question.



Apres avoir redemande a la personne en question, le vector
serait cree sur la pile ce qui pourrait causer des problemes
lors de la destruction (lorsque la fonction retourne)...C'est
pas plus clair que ca...


La question alors est : qu'est-ce que tu fais avec ce vector
dans ton constructeur ? Parce qu'il serait bien détruit à la fin
de l'expression qui invoque le constructeur. Alors, si tu en as
fait une copie, aucun problème. Sinon, il risque d'avoir des
problèmes dans tous les cas, même quand tu te sers pas de la
valeur par défaut.

Le mot de la fin etant "This is definitely not a good way how
to ensure that something is initialized."


Ce n'est pas une bonne solution pour initialiser une référence
ou un pointeur dans l'objet, c'est sûr. Mais si tu copies le
vector, il ne doit pas y avoir de problèmes.

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



Avatar
drkm
James Kanze writes:

korchkidu wrote:

Le mot de la fin etant "This is definitely not a good way how
to ensure that something is initialized."


Ce n'est pas une bonne solution pour initialiser une référence
ou un pointeur dans l'objet, c'est sûr.


Prendre l'adresse d'un objet passé par référence constante (et le
stocker pour après la fin de la fonction), c'est vicieux. Et si ce
paramètre a de plus une valeur par défaut ...

Mais ce n'est alors pas le fait d'avoir une valeur par défaut à
mettre en cause, ÀMHA, mais le passage par référence plutôt que d'un
pointeur.

--drkm


Avatar
Vladimir Votiakov
J'ai envie d'appeler ce genre de code de l'abus de intelligence
de compilateur.

A quoi ça sert de créer un objet sur stack pour le balancer aussitôt ?

A mon avis, il est plus logique d'avoir 2 constructeurs
sans paramètre et avec le paramètre et si tu veut
partager du code entre les deux, une petite fonction
(inline) initialize(), par exemple.


"Fabien LE LEZ" wrote in message
news:
On Tue, 04 Jan 2005 13:29:38 +0100, korchkidu :

J'avais donc simplement
utilise un parametre par defaut qui mettait le vector vide s'il n'etait
pas initialise. Mais dernierement, on me dit (avec des explications
fumeuses que je n'ai pas comprises...) qu'il fallait pas faire ca.


Ah ? Je veux bien voir les explications en question.
Personnellement, la construction suivante ne m'a jamais gêné :

class C
{
public:
C (int i, std::vector<double> const& v= std::vector<double>());
};



--
;-)



Avatar
drkm
"Vladimir Votiakov" writes:

J'ai envie d'appeler ce genre de code de l'abus de intelligence
de compilateur.


Que vient faire ici l'intelligence du compilateur ?

A quoi ça sert de créer un objet sur stack pour le balancer aussitôt ?


Tout dépend de ce que l'on appelle « aussitôt ». Le temps
d'exécution d'une fonction ne me semble pas un grain trop fin.

A mon avis, il est plus logique d'avoir 2 constructeurs
sans paramètre et avec le paramètre et si tu veut
partager du code entre les deux, une petite fonction
(inline) initialize(), par exemple.


Dans le cas présent, je ne vois pas l'utilité. Ça me semblerait
presque de l'obfuscation, dans le cas présent. On parle bien de
quelque chose comme :

void f( T const & t = T() ) {
// ...
}

vs. :

void f() {
T t ;
init( t ) ;
}

void f( T const & t ) {
init( t ) ;
}

void init( T const & t ) {
// ...
}

?

--drkm

Avatar
korchkidu
James Kanze wrote:

La question alors est : qu'est-ce que tu fais avec ce vector
dans ton constructeur ?
C'est un vecteur de pointeurs que j'itere pour initialiser mes donnees.


Parce qu'il serait bien détruit à la fin
de l'expression qui invoque le constructeur. Alors, si tu en as
fait une copie, aucun problème.
C'est des pointeurs sur des objets qui ne sont jamais detruits.


K.

Avatar
korchkidu
Vladimir Votiakov wrote:

A quoi ça sert de créer un objet sur stack pour le balancer aussitôt ?
De cette maniere j'ai un constructeur au lieu de 2.


K.

Avatar
Marc Boyer
Vladimir Votiakov wrote:
J'ai envie d'appeler ce genre de code de l'abus de intelligence
de compilateur.


Non, on transfère du coût de développement sur du coût CPU.
Ca me paraît une démarche constante dans l'histoire de l'informatique
et raisonnable en plus.

A quoi ça sert de créer un objet sur stack pour le balancer aussitôt ?


Eviter de passer du temps à écrire les deux constructeurs qui ne
sont qu'une coquille vide, éviter d'avoir à passer du temps
à documenter ce fouillis, éviter de faire perdre du temps
à ce qui liront la doc de la classe dans 8 mois, etc.

A mon avis, il est plus logique d'avoir 2 constructeurs
sans paramètre et avec le paramètre et si tu veut
partager du code entre les deux, une petite fonction
(inline) initialize(), par exemple.


Sauf que cela force soit à de la redondance de code, soit
à des initialisation inutiles pour les autres attributs.
Si on prend un système du genre:

F(int i, std::vector<double> v = std::vector<double>() ):
unAttribut(i), unAutre(v) {
// code de construction en rab
}

vs

F(int i, std::vector<double> v = std::vector<double>() ) {
init(i,v);
}

de toute façon, on paye la construction de unAttribut et
unAutre avec les valeurs par défaut, qu'ensuite on modifiera
dans init.

Marc Boyer
--
Je ne respecte plus le code de la route à vélo depuis une double fracture
due au fait que j'étais le seul à le respecter.

Avatar
kanze
drkm wrote:
James Kanze writes:

korchkidu wrote:

Le mot de la fin etant "This is definitely not a good way
how to ensure that something is initialized."


Ce n'est pas une bonne solution pour initialiser une
référence ou un pointeur dans l'objet, c'est sûr.


Prendre l'adresse d'un objet passé par référence constante
(et le stocker pour après la fin de la fonction), c'est
vicieux. Et si ce paramètre a de plus une valeur par défaut
...


Ça dépend des conventions de codage. Si certaines conventions
précisent l'utilisation d'un pointeur dès qu'on exige une durée
de vie supérieur à l'appel, d'autres précisent l'utilisation
d'une référence dès qu'on n'accepte pas un pointeur nul. Et
beaucoup exige le const systèmatiquement dans le cas où on ne
modifie pas le paramètre.

Mais ce n'est alors pas le fait d'avoir une valeur par
défaut à mettre en cause, ÀMHA, mais le passage par référence
plutôt que d'un pointeur.


Ce n'est pas la valeur par défaut en soi, mais le fait de
spécifier quelque chose qui ne va pas comme valeur par défaut.
Si le contrat de la fonction exige une durée de vie particulière
au paramètre, lui passer un temporaire est une erreur. Et donner
une valeur par défaut qui ne convient pas au contrat est
évidemment une chose à ne pas faire. (Lui passer une variable
statique est tout à fait acceptable, en revanche.)

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



Avatar
kanze
korchkidu wrote:
James Kanze wrote:

La question alors est : qu'est-ce que tu fais avec ce vector
dans ton constructeur ?


C'est un vecteur de pointeurs que j'itere pour initialiser mes
donnees.

Parce qu'il serait bien détruit à la fin de l'expression qui
invoque le constructeur. Alors, si tu en as fait une copie,
aucun problème.


C'est des pointeurs sur des objets qui ne sont jamais detruits.


D'accord. Le seul problème que ton exemple pourrait posé, c'est
un problème de durée de vie. Sinon, ça doit marcher sans
problème.

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


Avatar
kanze
Olivier Azeau wrote:
drkm wrote:
"Vladimir Votiakov" writes:
A quoi ça sert de créer un objet sur stack pour le balancer
aussitôt ?
Tout dépend de ce que l'on appelle « aussitôt ». Le temps

d'exécution d'une fonction ne me semble pas un grain trop
fin.


J'aurais plutôt dit : à quoi sert de créer un objet temporaire
si on ne va pas s'en servir ? Dans le cas présent, je ne vois
pas à quoi va bien pouvoir servir le vecteur vide.


A priori, il va s'en servir. Si j'ai bien compris, le but, c'est
que l'utilisateur passe 0-n paramètre d'un type donné, et qu'on
ne connaît pas forcement le nombre de paramètres lors de la
compilation.

Le paramèter par défaut, ce n'est qu'une commodité pour
l'utilisateur, pour qu'il n'ait pas besoin de créer lui-même un
vecteur vide dans le cas où le nombre de paramètres est 0.

A mon avis, il est plus logique d'avoir 2 constructeurs sans
paramètre et avec le paramètre et si tu veut partager du
code entre les deux, une petite fonction (inline)
initialize(), par exemple.


Dans le cas présent, je ne vois pas l'utilité. Ça me
semblerait presque de l'obfuscation, dans le cas présent. On
parle bien de quelque chose comme :

void f( T const & t = T() ) {
// ...
}

vs. :

void f() {
T t ;
init( t ) ;
}

void f( T const & t ) {
init( t ) ;
}

void init( T const & t ) {
// ...
}

?


Ce qui manque dans ton exemple, c'est le contenu de la méthode
"init" : A-t-on vraiment besoin d'écrire un code générique
pour les 2 cas de surcharge, surtout si T est un vecteur et
que l'on ne va probablement pas faire le même chose quand le
vecteur est vide ?


Pourquoi est-ce qu'on ferait quelque chose de différent si le
vector est vide ? Ne connaissant pas l'application, c'est
difficile de dire plus, mais a priori, je ne vois pas de
problèmes, ni au niveau du C++, ni au niveau de la conception.

cf Item 24 dans Effective C++.

Une autre façon de voir l'obfuscation, c'est le fait de
privilégier des écritures génériques aux écritures qui
expriment clairement l'intention du développeur.


Tout à fait. Mais de l'autre côté, les cas spéciaux sont aussi
de l'obfuscation. Pourquoi traiter le cas de zéro paramètres
différemment que le cas de un, ou de deux, ou de n ?

Dans le cas présent, si on a besoin de créer 3 itérateurs sur
le vecteur,


Pourquoi est-ce qu'on créerait trois itérateurs ? Deux, c'est
nécessaire, parce que c'est comme ça que fonctionne les
itérateurs. Mais j'ai bien le cas dans mon code où tout ce que
je fais, c'est de copier le vecteur dans un vecteur local (pour
des raisons de gestion de la durée de vie). Quelque soit le
nombre d'éléments, y compris zéro.

Ce qu'on pourrait éventuellement considérer, c'est de fournir
une fonction générique avec des itérateurs, plutôt qu'une
fonction unique avec un paramètre par défaut. Dans ce cas-là, on
ne pourrait évidemment pas se servir du paramètre par défaut.
Mais comme tu as dit, un excès de généricité n'aide pas la
clairté. À sa place, je garderais la fonction unique avec
paramètre par défaut pour l'instant ; si le besoin de plus de
flexibilité se fait sentir par la suite, c'est facile à ajouter
quelque chose du genre :

template< typename FwdIter >
void
f( FwdIter begin, FwdIter end )
{
init( std::vector< T >( begin, end ) ) ;
}

il y a fort à parier que ni la méthode init( T const & t ), ni
la valeur par défaut ne sont justifiés.


Comment pourrais-tu dire ça sans savoir ce que c'est
l'application ? Dans ce qu'il a posté jusqu'ici, il n'a rien dit
qui me fait croire qu'il est incompétent. Dans l'absense des
informations contraire, je suppose donc que sa décision est
justifiée.

(ce qui n'empêche pas de conserver une autre méthode init sans
paramètre pour partager du code) :

void f() {
// code d'initialisation avec t "vide"
init() ;
}

void f( T const & t ) {
// code d'initialisation avec t "non vide"
init() ;
}

void init() {
// ...
}


Ce qui me semble une complication et de l'obfuscation
supplémentaire par rapport à :

void
init( std::vector< T > const& params )
{
// ...
std::for_each( params.begin(), params.end(), ... ) ;
}

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



1 2 3 4 5