Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

initialisation des variables membres par défaut

73 réponses
Avatar
Richard Delorme
// ---------8<----------------------------
#include <iostream>

class A {
private:
int x;

public:
A() {};
~A() {};
void print() {
std::cout << x << std::endl;
}
};


int main() {
A t;
int y;


t.print();
std::cout << y << std::endl;

return 0;
}
// ---------8<----------------------------

Dans l'exemple ci-dessus, ni x, membre de la classe A, ni la variable
locale y ne sont initialisées.
gcc détecte le cas de la variable locale :
attention : ‘y’ may be used uninitialized in this function
mais reste silencieux sur la variable membre.
Valgrind signale les deux cas (de manière guère compréhensible d'ailleurs).

Personnellement je pensais comme Valgrind que la variable x devait être
initialisé explicitement dans le constructeur, par exemple en écrivant :
A() : x() {};
à la place du constructeur ci-dessus.

Néanmoins la lecture de blogs sur internet me laisse dans le doute. Par
exemple ici :
http://discuss.joelonsoftware.com/default.asp?joel.3.489111.50

On lit tout et son contraire. Qu'en est il en pratique ? (et en théorie
aussi d'ailleurs).

--
Richard

10 réponses

1 2 3 4 5
Avatar
JM Marino
On 26 jan, 11:42, Fabien LE LEZ wrote:
On Tue, 26 Jan 2010 11:27:12 +0100, Wykaaa :

>Une recommandation de "bonne programmation" est d'initialiser les
>variables le plus t t possible :

Avec un b mol : pour initialiser une variable d'un type de base, il
faut conna tre la valeur lui assigner.
Pour une variable num rique, on peut toujours utiliser 42, mais bon...



c'est bien pour cette raison qu'en Objective-C l'initialisation des
instances suit imédiatement leur allocation.

par exemple:
NSObject myObj = [[NSObjet] alloc] initWithInteger:100];

Tout ça pour confirmer que la bonne pratique est bien d'initialiser le
plus tôt possible les membres d'une classe.
Avatar
Wykaaa
Fabien LE LEZ a écrit :
On Tue, 26 Jan 2010 11:27:12 +0100, Wykaaa :

Une recommandation de "bonne programmation" est d'initialiser les
variables le plus tôt possible :



Avec un bémol : pour initialiser une variable d'un type de base, il
faut connaître la valeur à lui assigner.



Dans la plupart des cas, l'utilisation d'une variable dans un algorithme
impose sa valeur de départ. Pour les données lues, on peut initialiser à
une valeur qui ne sera jamais rencontrée dans la réalité. Ceci permet,
en phase de débogage de savoir si la valeur lue a correctement ou non
alimenté la variable.

Pour une variable numérique, on peut toujours utiliser 42, mais bon...



Douglas Adams est grand !
Avatar
Stan
On 26 jan, 11:47, Alain Ketterlin wrote:
Stan writes:
> On 25 jan, 18:09, (Bruno Causse) wrote:
>> Marc Espie wrote:
>> > Et les automatiques ne sont pas initialisees, s'en
>> > servir avant de leur donner une valeur EST une erreur.

>> oui, mais doivent elles etre initialisées dans le constructeur comme le
>> laisse suposer Valgrind?

> Oui mais pas avec cette forme : A() : x() { } !

Si si. Ca s'appelle "default initialization". Un exemple au hasard:



Je sais comment ça s'appelle ;-)

Mais que penses-tu de ce code :

class Class
{
public:
int A;
Class() : A(){ };
};

Class Cg;

int main()
{
Class Ca;

std::cout << "Class globale A= " << Cg.A << std::endl;
std::cout << "Class auto A= " << Ca.A << std::endl;
return 0;
}

?
--
-Stan
Avatar
Jean-Marc Bourguet
Stan writes:

On 26 jan, 11:47, Alain Ketterlin wrote:
> Stan writes:
> > On 25 jan, 18:09, (Bruno Causse) wrote:
> >> Marc Espie wrote:
> >> > Et les automatiques ne sont pas initialisees, s'en
> >> > servir avant de leur donner une valeur EST une erreur.
>
> >> oui, mais doivent elles etre initialisées dans le constructeur comme le
> >> laisse suposer Valgrind?
>
> > Oui mais pas avec cette forme : A() : x() { } !
>
> Si si. Ca s'appelle "default initialization". Un exemple au hasard:

Je sais comment ça s'appelle ;-)

Mais que penses-tu de ce code :

class Class
{
public:
int A;
Class() : A(){ };
};

Class Cg;

int main()
{
Class Ca;

std::cout << "Class globale A= " << Cg.A << std::endl;
std::cout << "Class auto A= " << Ca.A << std::endl;
return 0;
}



Ca.A est initialise a 0. Ce n'est pas le cas s'il n'y a pas de A() dans la
liste d'initialisation.

Essaie avec

{
Class Ca;

std::cout << "Class globale A= " << Cg.A << std::endl;
std::cout << "Class auto A= " << Ca.A << std::endl;
Ca.A = 42;
std::cout << "Class auto A= " << Ca.A << std::endl;
std::cout << "&Ca.A=" << (void*)&Ca.A << std::endl;
}
{
Class Ca;

std::cout << "Class globale A= " << Cg.A << std::endl;
std::cout << "Class auto A= " << Ca.A << std::endl;
std::cout << "&Ca.A=" << (void*)&Ca.A << std::endl;
}

si tu veux.

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
Avatar
Stan
On 26 jan, 13:55, Jean-Marc Bourguet wrote:

Essaie avec

{
Class Ca;

std::cout << "Class globale A= " << Cg.A << std::endl;
std::cout << "Class auto A= " << Ca.A << std::endl;
Ca.A = 42;
std::cout << "Class auto A= " << Ca.A << std::endl;
std::cout << "&Ca.A=" << (void*)&Ca.A << std::endl;
}
{
Class Ca;

std::cout << "Class globale A= " << Cg.A << std::endl;
std::cout << "Class auto A= " << Ca.A << std::endl;
std::cout << "&Ca.A=" << (void*)&Ca.A << std::endl;
}

si tu veux.



Voici le résultat avec VC6++ ( je n'ai que ça sous la main ) :

Class globale A= 0
Class auto A= -858993460
Class auto A= 42
&Ca.A12FF7C
Class globale A= 0
Class auto A= -858993460
&Ca.A12FF78

--
-Stan
Avatar
Jean-Marc Bourguet
Stan writes:

On 26 jan, 13:55, Jean-Marc Bourguet wrote:
>
>Essaie avec
>
> {
> Class Ca;
>
> std::cout << "Class globale A= " << Cg.A << std::endl;
> std::cout << "Class auto A= " << Ca.A << std::endl;
> Ca.A = 42;
> std::cout << "Class auto A= " << Ca.A << std::endl;
> std::cout << "&Ca.A=" << (void*)&Ca.A << std::endl;
> }
> {
> Class Ca;
>
> std::cout << "Class globale A= " << Cg.A << std::endl;
> std::cout << "Class auto A= " << Ca.A << std::endl;
> std::cout << "&Ca.A=" << (void*)&Ca.A << std::endl;
> }
>
>si tu veux.

Voici le résultat avec VC6++ ( je n'ai que ça sous la main ) :

Class globale A= 0
Class auto A= -858993460
Class auto A= 42
&Ca.A12FF7C
Class globale A= 0
Class auto A= -858993460
&Ca.A12FF78



VC6++ n'est pas conforme sur ce point (ref. 12.6.2/3 et 8.5/5).

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
Avatar
Stan
On 26 jan, 13:36, Wykaaa wrote:

Dans la plupart des cas, l'utilisation d'une variable dans un algorithme
impose sa valeur de départ. Pour les données lues, on peut initialise r à
une valeur qui ne sera jamais rencontrée dans la réalité. Ceci perm et,
en phase de débogage de savoir si la valeur lue a correctement ou non
alimenté la variable.




Si la variable n'est pas initialisée ( car pas connue)
dans le constructeur, son utilisation dépendra
probablement de l'ordre d'appel d'une ou plusieurs
fonctions membres; rien que pour cette raison je
protégerai cela par une post-condition dépendant d'une...
...initialisation dans le ctor ( avec quarante-deux, bien
entendu ;-) ).

--
-Stan
Avatar
Richard Delorme
Le 26/01/2010 11:27, Wykaaa a écrit :
Marc Espie a écrit :
In article <1jcvzpe.gc7981kl8f1wN%,
Bruno Causse wrote:
Marc Espie wrote:

Et les automatiques ne sont pas initialisees, s'en
servir avant de leur donner une valeur EST une erreur.


oui, mais doivent elles etre initialisées dans le constructeur comme le
laisse suposer Valgrind?



Non, elles ne *doivent* pas etre initialisees dans le constructeur.
Il suffit qu'elles soient initialisees avant d'utiliser leur valeur.
(et dans beaucoup de cas, la seule facon d'en etre sur, c'est de les
initialiser dans le constructeur).



Une recommandation de "bonne programmation" est d'initialiser les
variables le plus tôt possible :
- sur leur définition pour les variables "classiques"
- dans le constructeur pour les variables d'instances de classe



La réalité peut être plus complexe. En pratique une classe peut-être
assez compliquée et n'avoir besoin que d'une partie de ses variables
initialisées selon la situation. Tout initialiser dans le constructeur
(ou pire, avoir un compilateur qui initialiserait tout par défaut) peut
causer des problèmes de performances. L'idéal est de n'initialiser que
ce qui est nécessaire avant son utilisation.

De nombreuses études ont montré que l'erreur de programmation la plus
fréquente est d'utiliser ou de tester une variable non initialisée, ce
qui donne des défauts souvent difficilement reproductibles car le
comportement du programme dépend alors de la valeur contenue dans la
mémoire qui correspond à la variable au moment de l'exécution.



Tout à fait. Mais il existe des outils, comme Valgrind, pour détecter ce
genre de problème. A mon avis c'est plus efficace que de tout
initialiser dans le constructeur, par principe et sans réfléchir.
Surtout qu'une telle pratique ne mettrait pas à l'abri d'un oubli
(l'erreur étant humaine), tandis que l'utilisation d'outil comme
Valgrind sur des tests grandeur nature permet la détection de tels outils.

--
Richard
Avatar
espie
In article <4b5f22b7$0$17500$,
Richard Delorme wrote:
La réalité peut être plus complexe. En pratique une classe peut-être
assez compliquée et n'avoir besoin que d'une partie de ses variables
initialisées selon la situation. Tout initialiser dans le constructeur
(ou pire, avoir un compilateur qui initialiserait tout par défaut) peut
causer des problèmes de performances. L'idéal est de n'initialiser que
ce qui est nécessaire avant son utilisation.




Ca pue (au sens anti-pattern). Si ta classe est assez complexe pour
pouvoir tomber sur ce genre de choses, c'est generalement que tu essaies de
lui faire faire trop de choses, et qu'elle gagnerait grandement a etre
decoupee en morceaux plus petits, pour lesquels ce genre de souci ne se
poserait plus.

Tout à fait. Mais il existe des outils, comme Valgrind, pour détecter ce
genre de problème. A mon avis c'est plus efficace que de tout
initialiser dans le constructeur, par principe et sans réfléchir.
Surtout qu'une telle pratique ne mettrait pas à l'abri d'un oubli
(l'erreur étant humaine), tandis que l'utilisation d'outil comme
Valgrind sur des tests grandeur nature permet la détection de tels outils.



Quant a valgrind... je suis un fervent adepte de la programmation "a la main".
Les outils qui permettent de verifier les choses, c'est toujours bon a
prendre. Ca presente entre autres un fort interet pedagogique pour essayer
de rentrer quelques bonnes notions dans la tete au neuneu moyen.
Mais on se retrouve vite a trop se reposer dessus, et a faire des cochonneries
en se disant "de toutes facons, si c'est pas bon, valgrind va me sauver".
Mouais bof. Valgrind est cense etre plus con que le programmeur, quand meme,
et il va chopper toute une gamme d'erreurs idiotes, et en laisser passer
quelques-unes malgre tout.
Avatar
Stan
On 26 jan, 19:35, (Marc Espie) wrote:
In article <4b5f22b7$0$17500$,
Richard Delorme   wrote:

>La réalité peut être plus complexe. En pratique une classe peut- être
>assez compliquée et n'avoir besoin que d'une partie de ses variables
>initialisées selon la situation. Tout initialiser dans le constructeur
>(ou pire, avoir un compilateur qui initialiserait tout par défaut) peu t
>causer des problèmes de performances. L'idéal est de n'initialiser q ue
>ce qui est nécessaire avant son utilisation.

Ca pue (au sens anti-pattern). Si ta classe est assez complexe pour
pouvoir tomber sur ce genre de choses, c'est generalement que tu essaies de
lui faire faire trop de choses, et qu'elle gagnerait grandement a etre
decoupee en morceaux plus petits, pour lesquels ce genre de souci ne se
poserait plus.




Entièrement d'accord.

Un exemple classique d'excès est de vouloir rajouter des services en
rajoutant des fonctions membres à la classe quand ceux-ci
devraient/pourraient être des fonctions non-membre :

class Sheet
{
...
void addMargin();
void addTitle();
void addGrid();
...

};

C'est tentant de rajouter :

class Sheet
{
...
void addMargin();
void addTitle();
void addGrid();

void addAllStyles(); // appelle addMargin(), addTitle(), addGrid();
...

};

Alors qu'il préférable de faire :
void doSheetWithAllStyles (Sheet& sheet)
{
sheet.addMargin();
sheet.addTitle();
sheet.addGrid();
}


le tout peut être inclus dans un namespace :
namespace SheetForPreview {

class Sheet { ...};
void doSheetWithAllStyles (Sheet& sheet);

}

Bref, il y a beaucoup de moyens pour éviter
que la classe ne deviennent une usine à gaz,
en garantissant souplesse et lisibilité.

--
-Stan
1 2 3 4 5