OVH Cloud OVH Cloud

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
James Kanze
On Jan 25, 4:45 pm, (Marc Espie) wrote:
In article
,



Stan wrote:
>On 24 jan, 23:37, Fabien LE LEZ wrote:



>> Ta ligne>A() : x() {};



>> initialise x une valeur par d faut, qui se trouve tre 0 si
>> mes souvenirs sont bons. a ne me para t pas excessivement
>> utile.



C'est éventuellement mieux qu'un comportement indéfini.

>Je pense que ça dépend de l'architecture matériel parce que,
>si la classe est instanciée en globale , x vaudra zéro, mais
>sur la pile, elle sera indéfinie ( VC sur x86 ).



Non, ca ne depend pas. C'est parfaitement defini par la norme. Les
globales/static sont initialisees a 0 (que des bits a zero),



Pas forcément que des bits à zéro, mais plutôt comme si on
avait initialisé l'élément avec 0 (donc, 0.0 pour les flottant,
et un pointeur nul pour les pointeurs). Et il y a des
exceptions : dans une union, il n'y a que le premier élément qui
est initialisé, et les références ne sont pas initialisées
(puisqu'on ne peut pas les initialiser avec des nuls).

et ca fait effectivement 0 pour des valeurs numeriques
entieres, independamment de l'archi materielle. Et les
automatiques ne sont pas initialisees, s'en servir avant de
leur donner une valeur EST une erreur.



Sauf que si l'initialisation s'écrit avec un (), les
non-statiques sont initialisés comme des statiques.
(Évidemment, quelque chose comme :
int x();
au niveau de bloc ne marche pas, parce qu'il déclare une
fonction, plutôt que de définir une variable. Mais dans tous les
autres contextes, int() sera légal, et assurera
l'initialisation.)

--
James Kanze
Avatar
James Kanze
On Jan 26, 1:15 pm, Jean-Marc Bourguet wrote:
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).



Pour la bonne raison que VC++ 6 est sorti avant que cette règle
soit adoptée. (Je ne sais pas pourquoi tellement de gens
s'obstinent à utiliser un compilateur aussi ancien.)

--
James Kanze
Avatar
Richard Delorme
Le 26/01/2010 19:35, Marc Espie a écrit :
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.



Pas nécessairement. Je prend un exemple, un coup dans un jeu d'échec.
Une implémentation simplifiée sous forme de classe pourrait être :

class Move {
// fonctions membres privées
public:
struct {
Coordinate x;
Piece piece;
} from, to;
Piece promotion;
int value;

// fonctions membres publiques.
}

Pourquoi initialiser la variable promotion si la pièce de départ n'est
pas un pion, où, même si c'est un pion, la case d'arrivée n'est pas sur
la dernière rangée ?
La valeur du coup, la variable value, est issue d'un long calcul. A quoi
bon y mettre une valeur bidon ?
Pour moi, l'état indéterminée d'une variable a du sens et je ne
comprends pas cette volonté de mettre une valeur déterminée à tout prix
pour tout.
En pratique, cette classe ne contient ni constructeur ni destructeur.
Les données du coup sont initialisées directement depuis son conteneur
(un tableau de coups), car il est plus simple de générer l'ensemble des
coups (ou tout du moins d'une famille de coups), qu'un coup isolé.

Peut-être cette façon de procéder est-elle un anti-pattern, mais c'est
la manière idiomatique qu'utilise tous les programmes d'un niveau
acceptable écrit en C++.


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.



Pas du tout. Moi, je suis faillible et j'écris, à la main, malgré tous
le soin que je peux prendre, une connerie toutes les cent lignes. Le
compilateur en détecte 99% à la compilation, mais il reste une connerie
toutes les dix-mille lignes. Les outils tels que valgrind me permette de
détecter certaines de ces erreurs (variable non initialisée, fuite de
mémoire, débordement de tableau, etc.). Pour moi l'intéret pédagogique
est nul et valgrind ne m'a jamais rien appris, mais il m'évite des bugs,
parfois sournois.

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



Je n'ai jamais dit qu'il fallait se reposer entièrement dessus et
programmer sans soin. Je dit juste que malgré tous les soins que l'on
peut apporter, on fait des erreurs et que valgrind est un outil utile
dans leur détection.

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.



Je n'ai jamais dit que Valgrind était une arme infaillible, il ne
détectera jamais la fausseté d'un algorithme. Je le préfère néanmoins à
un débogueur, car il détecte les erreurs en amont (quand elles
apparaissent), et non en aval (quand le programme crash).

--
Richard
Avatar
Wykaaa
Richard Delorme a écrit :
Le 26/01/2010 19:35, Marc Espie a écrit :
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.





Entièrement d'accord avec toi.

Pas nécessairement. Je prend un exemple, un coup dans un jeu d'échec.
Une implémentation simplifiée sous forme de classe pourrait être :

class Move {
// fonctions membres privées
public:
struct {
Coordinate x;
Piece piece;
} from, to;
Piece promotion;
int value;

// fonctions membres publiques.
}

Pourquoi initialiser la variable promotion si la pièce de départ n'est
pas un pion, où, même si c'est un pion, la case d'arrivée n'est pas sur
la dernière rangée ?



Cette classe en fait déjà trop. Pourquoi mélanger le coup avec la
promotion ?

La valeur du coup, la variable value, est issue d'un long calcul. A quoi
bon y mettre une valeur bidon ?



Si, à l'initialisation d'une instance de classe, il y a des attributs
qu'on ne sait pas initialiser dans le constructeur, c'est qu'il y a un
défaut de conception. C'est le cas de ta classe Move car "promotion" ne
doit pas se trouver là.

Pour moi, l'état indéterminée d'une variable a du sens et je ne
comprends pas cette volonté de mettre une valeur déterminée à tout prix
pour tout.



Non, il n'y a aucun sens à avoir des variables avec un état indéterminé.
C'est forcément le signe d'un défaut de conception quelque part.

En pratique, cette classe ne contient ni constructeur ni destructeur.



C'est déjà mauvais signe.

Les données du coup sont initialisées directement depuis son conteneur
(un tableau de coups), car il est plus simple de générer l'ensemble des
coups (ou tout du moins d'une famille de coups), qu'un coup isolé.



D'accord mais ce n'est pas parce que l'on va faire comme ça que la
classe qui décrit 1 coup ne doit pas avoir de constructeur pour
initialiser ses propres attributs.

Peut-être cette façon de procéder est-elle un anti-pattern, mais c'est
la manière idiomatique qu'utilise tous les programmes d'un niveau
acceptable écrit en C++.



Ton exemple est totalement anti-pattern, effectivement.
Ce n'est pas parce que la plupart des programmes C++ sont mal écrits
(mettons au moins 90%) qu'il faut justifier cette manière de faire.

[couic valgrind : mais je suis d'accord avec Marc]
Avatar
Serge Paccalin
Le Wed, 27 Jan 2010 07:33:25 +0100, Richard Delorme a écrit
(dans <news:4b5fded2$0$928$, posté
dans fr.comp.lang.c++) :

Pour moi, l'état indéterminée d'une variable a du sens et je ne
comprends pas cette volonté de mettre une valeur déterminée à tout prix
pour tout.



Le problème, c'est que « indéterminé » varie d'une instance à l'autre,
et peut ressembler parfois à une valeur déterminée.

L'intérêt d'initialiser, c'est d'avoir un comportement reproductible en
cas d'accès erroné.

Pour reprendre les échecs, où il y a six types de pièces (roi, reine,
cavalier, tour, fou, pion), une pièce non initialisée pourrait
accidentellement avoir l'un ou l'autre de ces six types et engendrer des
erreurs non reproductibles. Il vaudrait mieux avoir alors un septième
type et initialiser explicitement.

--
___________
_/ _ _`_`_`_) Serge PACCALIN -- sp ad mailclub.net
_L_) Il faut donc que les hommes commencent
-'(__) par n'être pas fanatiques pour mériter
_/___(_) la tolérance. -- Voltaire, 1763
Avatar
Stan
On 27 jan, 07:33, Richard Delorme wrote:
Le 26/01/2010 19:35, Marc Espie a écrit :

> 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 constructe ur
>> (ou pire, avoir un compilateur qui initialiserait tout par défaut) p eut
>> 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 essaie s 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.

Pas nécessairement. Je prend un exemple, un coup dans un jeu d'échec.
Une implémentation simplifiée sous forme de classe pourrait être :

class Move {
// fonctions membres privées
public:
struct {
Coordinate x;
Piece piece;
} from, to;
Piece promotion;
int value;

// fonctions membres publiques.

}




Personnellent, je préfére voir Move comme une
transaction hors de l'objet pièce.
Et d'autre part, je ne conçois pas un objet Piece
sans position initiale.

Une idée simplifiée de ce à quoi je pense :

class Piece
{
private:
// position
int m_pos;
public:
Piece(int pos) : m_pos(pos) {};
virtual ~Piece() { };
virtual Affiche()=0;
int getPos() {return m_pos; };
};


class Pion : public Piece
{
public:
Pion(int pos) : Piece(pos) {};
virtual ~Pion() { };
virtual Affiche() {
std::cout << "Pion, pos=" << getPos() << std::endl;
}

};

class Reine : public Piece
{
public:
Reine(int pos) : Piece(pos) {};
virtual ~Reine() { };
virtual Affiche() {
std::cout << "Reine, pos=" << getPos() << std::endl;
}

};

class Echiquier
{
public:
Echiquier() {};
bool IsRangeePromotion(int pos) { return true; }

};

void Deplace ( Piece* piece, int nouvellePos, Echiquier& echiquier )
{

if (echiquier.IsRangeePromotion(nouvellePos) == true)
{
// on choisi la nouvelle piece: une reine pour l'exemple
delete piece;
piece = new Reine(nouvellePos);
}
}

Echiquier echiquier; // singleton

int main()
{

Piece* piece = new Pion(7); // un pion n'a de sens qu'avec une
position initiale

piece->Affiche();
Deplace(piece, 8, echiquier);
piece->Affiche();
return 0;
}

( désolé si la pagination est cassée par GoogleGroupe )


--
-Stan
Avatar
Richard Delorme
Le 27/01/2010 10:59, Stan a écrit :

Personnellent, je préfére voir Move comme une
transaction hors de l'objet pièce.



Non, dans un programme d'échecs la notion de coup est fondamentale et ne
peut se résumer à une fonction changeant un état : on trie des coups, on
cherche le meilleur coup, etc. Il doit être aussi suffisament complet
pour mettre à jour l'échiquier et le restaurer dans son état antérieur.
Enfin on doit pouvoir lire et écrire un coup selon des formats
normalisés (par la FIDE). Bref c'est vraiment quelque chose que l'on
doit pouvoir manipuler à son aise.


Et d'autre part, je ne conçois pas un objet Piece
sans position initiale.

Une idée simplifiée de ce à quoi je pense :

class Piece
{
private:
// position
int m_pos;



Chez moi, Piece est une simple énumération des types de pièces possibles
et l'échiquier est un tableau de pièces. Une classe similaire à ta
classe existe, elle s'appelle PieceLocation et sert à gérer une liste de
pièces qui est effectivement utile pour la génération des coups.

public:
Piece(int pos) : m_pos(pos) {};
virtual ~Piece() { };
virtual Affiche()=0;
int getPos() {return m_pos; };
};


class Pion : public Piece


[...]
class Reine : public Piece
{
public:
Reine(int pos) : Piece(pos) {};
virtual ~Reine() { };
virtual Affiche() {



Dans un programme d'échecs, ce type d'abstraction est l'exemple de ce
qu'il faut éviter.


void Deplace ( Piece* piece, int nouvellePos, Echiquier& echiquier )


[...]
delete piece;
piece = new Reine(nouvellePos);



Dans un programme d'échecs, il y a des choses qu'il faut vraiment
éviter, ce sont les allocations/déallocations partout ce qui affecte les
performances. Aujourd'hui, les programmes cherchent plusieurs millions
de coups/s.
En plus à l'usage ta fonction est assez compliqué à utiliser. Ma classe
Move je l'utilise comme ça (code simplifié):

int Search::alphabeta(int alpha, int beta, int depth)
MoveArray move_array;
Move *move, *bestmove = NULL;
/*...*/
board.generate_moves(move_array);
/*...*/
for (move = move_array.first_best();
move < move_array.end();
move = move_array.next_best(move)) {
board.update(*move);
move->value = -alphabeta(-beta, -alpha, depth - 1);
board.restore(*move);

if (!bestmove || move->value > bestmove->value) {
bestmove = move;
if (bestmove->value >= beta) break;
if (bestmove->value > alpha) alpha = bestmove->value;
}
}
/*...*/
}

Ta fonction Deplace on l'utilise comment là dedans ?
Avatar
Richard Delorme
Le 27/01/2010 10:59, Stan a écrit :
void Deplace ( Piece* piece, int nouvellePos, Echiquier& echiquier )
{

if (echiquier.IsRangeePromotion(nouvellePos) == true)
{
// on choisi la nouvelle piece: une reine pour l'exemple
delete piece;
piece = new Reine(nouvellePos);
}
}



Au fait ça ne marche pas ; il y a une erreur d'école. Il faut écrire :
void Deplace (Piece **piece, /*... */)
{
/*...*/
delete *piece;
*piece = new Reine(nouvellePos);
/*...*/
}

Preuve supplémentaire qu'il faut éviter les new/delete le plus possible ;-)

--
Richard
Avatar
Jean-Marc Bourguet
Richard Delorme writes:

Le 27/01/2010 10:59, Stan a écrit :
> void Deplace ( Piece* piece, int nouvellePos, Echiquier& echiquier )
> {
>
> if (echiquier.IsRangeePromotion(nouvellePos) == true)
> {
> // on choisi la nouvelle piece: une reine pour l'exemple
> delete piece;
> piece = new Reine(nouvellePos);
> }
> }

Au fait ça ne marche pas ; il y a une erreur d'école. Il faut écrire :
void Deplace (Piece **piece, /*... */)
{
/*...*/
delete *piece;
*piece = new Reine(nouvellePos);
/*...*/
}

Preuve supplémentaire qu'il faut éviter les new/delete le plus possible ;-)



Commencons alors par proposer...

void Deplace (Piece *&piece, /*... */)

:-)

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 27 jan, 14:48, Richard Delorme wrote:

Au fait ça ne marche pas ; il y a une erreur d'école. Il faut écrir e :
void Deplace (Piece **piece, /*... */)
{
/*...*/
delete *piece;
*piece = new Reine(nouvellePos);
/*...*/

}

Preuve supplémentaire qu'il faut éviter les new/delete le plus possib le ;-)




Arghhh. Erreur d'inattention, je n'ai pas dit que c'étais un cas
d'école, c'est brouillon.

Sinon, pour en revenir aux points que tu as soulevés, j'aimerai bien
que tu me dises
quels sont les paradigmes C++ que tu _utilises_ dans un programme de
jeux d'échecs,
parce que j'ai l'impression que l'aspect performance limite pas mal
les choses
( quid des conteners ?).

--
-Stan
1 2 3 4 5