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

Avatar
James Kanze
On Jan 27, 7:45 pm, Wykaaa wrote:
Marc Espie a écrit :


[...]
> Les variables non initialisees (et leur copain, les switch
> sur un champ type) sont des symptomes de design faits par
> des gens qui n'ont pas encore bien integre la conception
> objet (ou qui, pour de basses questions de temps, n'ont pas
> termine leur design).



En approche objet, je n'utilise jamais des enums. Je passe
toujours par l'héritage.



Étant donné que les deux sert à des buts complètement
différents, je ne vois pas le rapport. J'en utilise les deux.

Un enum, c'est une classe abstraite et il y a autant de
sous-classes que de valeurs dans l'enum.



Sauf qu'un enum, ça n'a rien à voir avec une classe, abstraite
ou non. (Pour commencer, un enum, c'est une valeur, et une
classe abstraite suppose en général un comportement et une
identité. Tout à fait l'opposer d'un enum.)

--
James Kanze
Avatar
Wykaaa
Stan a écrit :
On 27 jan, 20:45, Wykaaa wrote:

En approche objet, je n'utilise jamais des enums. Je passe toujours par
l'héritage.
Un enum, c'est une classe abstraite et il y a autant de sous-classes que
de valeurs dans l'enum.



Faut pas faire dans le dogmatisme.
Il faut utiliser les enums pour ce quoi elle sont utiles;
effectivement, si c'est pour faire un mécanisme de sélection
de type, le polymorphisme est plus adapté.
Mais dans ce cas, tu aurais en dire autant des switches.



La chasse aux switches est à la programmation objet ce que la chasse aux
gotos est à la programmation structurée.
Les enums conduisent inévitablement à des switches, c'est pour cela que
je ne les utilise pas.


J'ai vu beaucoup de projets où l'héritage était utilisé à outrance,
où tout était modélisé en classes :
ces programmes étaient difficiles à comprendre et à maintenir.



Ce qui est difficile à maintenir ce sont les switches dépendant d'énums
auxquels on va ajouter des valeurs (il va falloir faire tous les tests
de non régression) alors qu'avec l'héritage on ajoutera les sous-classes
correspondant aux valeurs ajoutées *sans toucher au code existant*. On
n'aura à tester que le code qui correspond aux nouvelles valeurs.
Où sont les difficultés de maintenance avec du code purement objet ?
Quant à la compréhension du code purement objet, il apparaît difficile
pour 2 raisons essentiellement :
1) Soit les gens qui le lisent ne sont pas suffisamment formés ou
habitués à l'objet
2) Soit le code est mal écrit (en fait c'est plutôt souvent parce que la
conception objet est mal foutue).

J'ai vu assez peu de gens savoir faire une vraie (et bonne) conception
objet. Quant au codage, n'en parlons même pas. Combien de classes, dans
du code réel, respectent les principes de forte cohésion et de faible
couplage, par exemple ? mais c'est un problème de conception avant tout.

J'ai audité des centaines de milliers de lignes de code C++ et Java,
souvent avec le logiciel Logiscope. J'ai défini mes propres modèles
qualité, dans ce logiciel, en fonction des types d'application avec des
facteurs et critères qualité comme la faible dépendance, la
maintenabilité, la modularité, le nombre moyen de méthodes et
d'attributs par classe, la résistance aux évolutions, la testabilité, etc.
Je peux t'assurer, qu'en général, ce n'était pas triste...
Les seuls résultats corrects que j'ai observés se trouvaient dans des
projets où une méthodologie et des règles de programmation en béton
avaient été définies et où elles étaient STRICTEMENT respectées.

Enfin, pour finir et enfoncer le clou, dans une approche objet, tout,
absolument tout, doit être modélisé en classes et le polymorphisme doit
être utilisé à tous les niveaux des hiérarchies de classes.

Faire autrement, dans des langages objets comme C++ ou Java, c'est
vouloir utiliser ces langages pour autre chose que ce pourquoi ils ont
été inventés.

Maintenant on peut aimer jouer à se faire peur en n'écoutant pas les
leçons de l'histoire de l'informatique mais quand on est dans
l'industrie, ça devient un problème d'éthique : on ne fait pas joujou
avec le code quand il s'agit de logiciels pour l'avionique ou les
centrales nucléaires. En plus,il y a des mécanismes de certification
draconiens.

Pour un jeu d'échec... faites donc si ça vous chante mais ce n'est pas
la voie non plus.
Avatar
Wykaaa
James Kanze a écrit :
On Jan 27, 7:45 pm, Wykaaa wrote:
Marc Espie a écrit :


[...]
Les variables non initialisees (et leur copain, les switch
sur un champ type) sont des symptomes de design faits par
des gens qui n'ont pas encore bien integre la conception
objet (ou qui, pour de basses questions de temps, n'ont pas
termine leur design).





En approche objet, je n'utilise jamais des enums. Je passe
toujours par l'héritage.



Étant donné que les deux sert à des buts complètement
différents, je ne vois pas le rapport. J'en utilise les deux.



Non, il n'ont pas des buts différents.

Un enum, c'est une classe abstraite et il y a autant de
sous-classes que de valeurs dans l'enum.



Sauf qu'un enum, ça n'a rien à voir avec une classe, abstraite
ou non. (Pour commencer, un enum, c'est une valeur, et une
classe abstraite suppose en général un comportement et une
identité. Tout à fait l'opposer d'un enum.)



Tu veux dire qu'un enum est une variable scalaire quand tu dis "valeur"?

Et un enum, ça a un comportement puisqu'on le gère avec des switches...

1) enum

enum Jour {LUNDI, MARDI, MERCREDI, ...};
Jour j = ...;

void travailler(Jour leJour)
{
switch(j)
{
case LUNDI : //faire ceci
case MARDI : //faire cela
case MERCREDI : //faire autre chose
...
}

travailler(j);
N'est-ce pas une fonction qui décrit un comportement de l'enum ?

2) polymorphisme

class Jour
{
public:
virtual void travailler()=0;
// éventuellement d'autres méthodes
}

class Lundi
{
public:
virtual void travailler() {//faire ceci}
};
class Mardi
{
public:
virtual void travailler() {//faire cela}
};
class Mercredi
{
public:
virtual void travailler() {//faire autre chose}
}

Jour j = new ...;

j.travailler();

Quel est le plus évolutif ?
Où est ta différence valeur/comportement ?

Et encore, j'ai pris un exemple ou il n'y aura pas de nouvelle valeur
pour l'enum mais si c'était le cas, quel est le plus simple à maintenir,
et même à comprendre sachant que si j'ai plusieurs méthodes sur Jour,
les switches seront éventuellement disséminés partout dans le code alors
que là, l'arborescence de Jour sera dans le module Jour.h/Jour.cpp
(forte cohésion)

C'est pareil pour les pièces de l'échiquier et Move devient une méthode
virtuelle de Pièce.
La promotion c'est la destruction de la pièce, objet de la promotion, et
la création, avec la même position (que je préfère à l'identifiant
"coordonnées") de la pièce qui la remplace.
Position est d'ailleurs elle-même une classe.

Si cela pose des problèmes de performances, alors il ne faut pas
utiliser C++ mais C, par exemple, avec force enums et switches mais ça
va être non maintenable. Cependant on sait que si c'est la performance
qui prime sur tous les autres facteurs, le code résultant n'est, en
général, plus maintenable.
Avatar
Stan
On 28 jan, 00:09, Wykaaa wrote:

[...]


Quel est le plus évolutif ?
Où est ta différence valeur/comportement ?




Même avec une conception objet du logiciel efficace,
à un moment donné, il te faudra bien implémenter
*un peu* d'algorithmie.

Et là, tu peux dire ce que tu veux, pour être efficace
(en rapidité, lisibilité et maintenance),
les enum, switch ou if/else sont tout adaptés.
Lorsque c'est bien, conçu, ce ne pose aucun pb
( à l'echelle de l'algo évidemment).


--
-Stan
Avatar
Wykaaa
Stan a écrit :
On 28 jan, 00:09, Wykaaa wrote:

[...]

Quel est le plus évolutif ?
Où est ta différence valeur/comportement ?




Même avec une conception objet du logiciel efficace,
à un moment donné, il te faudra bien implémenter
*un peu* d'algorithmie.

Et là, tu peux dire ce que tu veux, pour être efficace
(en rapidité, lisibilité et maintenance),
les enum, switch ou if/else sont tout adaptés.
Lorsque c'est bien, conçu, ce ne pose aucun pb
( à l'echelle de l'algo évidemment).



Mon exemple ne te parle-t-il pas ?
Les switches sont une horreur pour la maintenance et les évolutions
quand on fait de l'objet alors que le polymorphisme rend les
"aiguillages" automatiques quand on ajoute des sous-classes.
Avatar
Stan
On 28 jan, 12:53, Wykaaa wrote:

Mon exemple ne te parle-t-il pas ?



Non car tu as omis le plus important :

Jour j = new ...;



Comment détermines-tu le type à instancier ?

Les switches sont une horreur pour la maintenance et les évolutions
quand on fait de l'objet alors que le polymorphisme rend les
"aiguillages" automatiques quand on ajoute des sous-classes.



Et d'autre part, tu mets quoi dans tes pattern factory ?

--
-Stan
Avatar
Michael Doubez
On 28 jan, 00:09, Wykaaa wrote:
James Kanze a écrit :

> On Jan 27, 7:45 pm, Wykaaa wrote:
>> Marc Espie a écrit :
>     [...]
>>> Les variables non initialisees (et leur copain, les switch
>>> sur un champ type) sont des symptomes de design faits par
>>> des gens qui n'ont pas encore bien integre la conception
>>> objet (ou qui, pour de basses questions de temps, n'ont pas
>>> termine leur design).

>> En approche objet, je n'utilise jamais des enums. Je passe
>> toujours par l'héritage.

> Étant donné que les deux sert à des buts complètement
> différents, je ne vois pas le rapport. J'en utilise les deux.

Non, il n'ont pas des buts différents.



Formellement, un enum est un type distinct constitué d'un ensemble de
constantes.
C'est l'utilisation que tu en fais qui a un but similaire.

Plus généralement, ilms sont utilisés pour nommer une valeur; c'est
mieux qu'un define car ils ont une portée et à la préférence d'une
static de classe comme ça, il n'a pas besoin d'être défini (sauf quan d
des petits malins essayent de faire des enum sur une representation
plus large que int).


>> Un enum, c'est une classe abstraite et il y a autant de
>> sous-classes que de valeurs dans l'enum.

> Sauf qu'un enum, ça n'a rien à voir avec une classe, abstraite
> ou non. (Pour commencer, un enum, c'est une valeur, et une
> classe abstraite suppose en général un comportement et une
> identité. Tout à fait l'opposer d'un enum.)

Tu veux dire qu'un enum est une variable scalaire quand tu dis "valeur"?



Qui a la propriété interressant qu'à la déclaration, la valeur d'un
enum est la valeur de l'enum précédent + 1.


Et un enum, ça a un comportement puisqu'on le gère avec des switches. ..

1) enum

enum Jour {LUNDI, MARDI, MERCREDI, ...};
Jour j = ...;

void travailler(Jour leJour)
{
switch(j)
{
        case LUNDI : //faire ceci
        case MARDI : //faire cela
        case MERCREDI : //faire autre chose
        ...

}

travailler(j);
N'est-ce pas une fonction qui décrit un comportement de l'enum ?

2) polymorphisme

class Jour
{
public:
        virtual void travailler()=0;
        // éventuellement d'autres méthodes

}

class Lundi
{
public:
        virtual void travailler() {//faire ceci}};

class Mardi
{
public:
        virtual void travailler() {//faire cela}};

class Mercredi
{
public:
        virtual void travailler() {//faire autre chose}

}

Jour j = new ...;

j.travailler();

Quel est le plus évolutif ?
Où est ta différence valeur/comportement ?



La différence est que tu lies une notion d'identification (dans un
set) avec une notion d'execution associée.

En terme d'impact, ça veux dire que le jour ou l'interface travailler
() n'est plus nécessaire (c'est pas demain la veille) ou doit évoluer,
le code deviens énorme.

Alors que l'interface suivante décorelle les deux:

struct IJourDeTravail
{
virtual void travailler()=0;
// éventuellement d'autres méthodes
}

template<Jour J>
struct JourDeTravail;

template<>
struct JourDeTravail<Lundi>
{
virtual void travailler()
{
// ....
}
};

// même code
IJourDeTravaille j = new ...;
j.travailler();

Et encore, j'ai pris un exemple ou il n'y aura pas de nouvelle valeur
pour l'enum mais si c'était le cas, quel est le plus simple à mainten ir,
  et même à comprendre sachant que si j'ai plusieurs méthodes sur Jour,
les switches seront éventuellement disséminés partout dans le code alors
que là, l'arborescence de Jour sera dans le module Jour.h/Jour.cpp
(forte cohésion)

C'est pareil pour les pièces de l'échiquier et Move devient une mét hode
virtuelle de Pièce.
La promotion c'est la destruction de la pièce, objet de la promotion, e t
la création, avec la même position (que je préfère à l'identifi ant
"coordonnées") de la pièce qui la remplace.
Position est d'ailleurs elle-même une classe.



Dans le cas de l'échequier je ne suis pas sûr que la modélisation des
pièces soit un point central. Une recherche se fera plutôt sur l'état
d'un plateau, les pièces existeront en instance unique pour générer
les états de plateau suivant.

Suivant un tradeoff mémoire/cpu, le plateau sera composé de pointeurs
sur les pièces ou un tableau de char identifiant la pièce à une
position (donc un switch case).

Si cela pose des problèmes de performances, alors il ne faut pas
utiliser C++ mais C, par exemple, avec force enums et switches mais ça
va être non maintenable. Cependant on sait que si c'est la performance
qui prime sur tous les autres facteurs, le code résultant n'est, en
général, plus maintenable.



Et peut être aussi pas plus rapide.

--
Michael
Avatar
espie
In article ,
Michael Doubez wrote:
Dans le cas de l'échequier je ne suis pas sûr que la modélisation des
pièces soit un point central. Une recherche se fera plutôt sur l'état
d'un plateau, les pièces existeront en instance unique pour générer
les états de plateau suivant.



Normalement, on commence par realiser une conception logiquement fiable, et
ensuite on se pose des questions eventuelles d'efficacite.

Sur ce type d'exemple, je pense directement au pattern flyweight, par exemple.
Avatar
Wykaaa
Stan a écrit :
On 28 jan, 12:53, Wykaaa wrote:

Mon exemple ne te parle-t-il pas ?



Non car tu as omis le plus important :

Jour j = new ...;



Comment détermines-tu le type à instancier ?



Comme dans le cas des enums. Je ne vois pas ce que ça change ici. Ca
dépend de la façon dont tu exploites le jour. Je n'ai noté que les
différences entre les 2 façons de faire.
Je te retourne la question : comment détermines-tu la valeur
d'initialisation dans le cas de l'enum ?
C'est identique dans les 2 cas...

Les switches sont une horreur pour la maintenance et les évolutions
quand on fait de l'objet alors que le polymorphisme rend les
"aiguillages" automatiques quand on ajoute des sous-classes.



Et d'autre part, tu mets quoi dans tes pattern factory ?



Ce n'est pas la question ici car tu n'en a pas forcément besoin, mais si
tu veux passer par une AbstractFactory pour créer les jours, libre à toi
et je te renvoie, dans ce cas, à la littérature abondante qui explicite
la chose...
Avatar
Wykaaa
Michael Doubez a écrit :
On 28 jan, 00:09, Wykaaa wrote:
James Kanze a écrit :

On Jan 27, 7:45 pm, Wykaaa wrote:
Marc Espie a écrit :


[...]
Les variables non initialisees (et leur copain, les switch
sur un champ type) sont des symptomes de design faits par
des gens qui n'ont pas encore bien integre la conception
objet (ou qui, pour de basses questions de temps, n'ont pas
termine leur design).


En approche objet, je n'utilise jamais des enums. Je passe
toujours par l'héritage.


Étant donné que les deux sert à des buts complètement
différents, je ne vois pas le rapport. J'en utilise les deux.


Non, il n'ont pas des buts différents.



Formellement, un enum est un type distinct constitué d'un ensemble de
constantes.
C'est l'utilisation que tu en fais qui a un but similaire.



Oui. Je suis d'accord. Je me suis effectivement mal exprimé. Je voulais
dire que l'héritage pouvait servir à représenter des enums (pour moi,
les enums sont un cas particulier d'héritage).

Plus généralement, ilms sont utilisés pour nommer une valeur; c'est
mieux qu'un define car ils ont une portée et à la préférence d'une
static de classe comme ça, il n'a pas besoin d'être défini (sauf quand
des petits malins essayent de faire des enum sur une representation
plus large que int).



Oui, on est d'accord.


Un enum, c'est une classe abstraite et il y a autant de
sous-classes que de valeurs dans l'enum.


Sauf qu'un enum, ça n'a rien à voir avec une classe, abstraite
ou non. (Pour commencer, un enum, c'est une valeur, et une
classe abstraite suppose en général un comportement et une
identité. Tout à fait l'opposer d'un enum.)


Tu veux dire qu'un enum est une variable scalaire quand tu dis "valeur"?



Qui a la propriété interressant qu'à la déclaration, la valeur d'un
enum est la valeur de l'enum précédent + 1.



Pas pour la première valeur et pas si on donne une valeur de codage à
chaque enum. J'ai toujours trouvé stupide que la fonction succ() sur les
enums (en C, C+) soit implémentée avec la fonction incrément sur les
entiers...


Et un enum, ça a un comportement puisqu'on le gère avec des switches...

1) enum

enum Jour {LUNDI, MARDI, MERCREDI, ...};
Jour j = ...;

void travailler(Jour leJour)
{
switch(j)
{
case LUNDI : //faire ceci
case MARDI : //faire cela
case MERCREDI : //faire autre chose
...

}

travailler(j);
N'est-ce pas une fonction qui décrit un comportement de l'enum ?

2) polymorphisme

class Jour
{
public:
virtual void travailler()=0;
// éventuellement d'autres méthodes

}

class Lundi
{
public:
virtual void travailler() {//faire ceci}};

class Mardi
{
public:
virtual void travailler() {//faire cela}};

class Mercredi
{
public:
virtual void travailler() {//faire autre chose}

}

Jour j = new ...;

j.travailler();

Quel est le plus évolutif ?
Où est ta différence valeur/comportement ?



La différence est que tu lies une notion d'identification (dans un
set) avec une notion d'execution associée.

En terme d'impact, ça veux dire que le jour ou l'interface travailler
() n'est plus nécessaire (c'est pas demain la veille) ou doit évoluer,
le code deviens énorme.

Alors que l'interface suivante décorelle les deux:

struct IJourDeTravail
{
virtual void travailler()=0;
// éventuellement d'autres méthodes
}

template<Jour J>
struct JourDeTravail;

template<>
struct JourDeTravail<Lundi>
{
virtual void travailler()
{
// ....
}
};

// même code
IJourDeTravaille j = new ...;
j.travailler();



Je suis d'accord mais je voulais juste montrer les différences entre les
2 approches sans entrer dans les complications...

Et encore, j'ai pris un exemple ou il n'y aura pas de nouvelle valeur
pour l'enum mais si c'était le cas, quel est le plus simple à maintenir,
et même à comprendre sachant que si j'ai plusieurs méthodes sur Jour,
les switches seront éventuellement disséminés partout dans le code alors
que là, l'arborescence de Jour sera dans le module Jour.h/Jour.cpp
(forte cohésion)

C'est pareil pour les pièces de l'échiquier et Move devient une méthode
virtuelle de Pièce.
La promotion c'est la destruction de la pièce, objet de la promotion, et
la création, avec la même position (que je préfère à l'identifiant
"coordonnées") de la pièce qui la remplace.
Position est d'ailleurs elle-même une classe.



Dans le cas de l'échequier je ne suis pas sûr que la modélisation des
pièces soit un point central. Une recherche se fera plutôt sur l'état
d'un plateau, les pièces existeront en instance unique pour générer
les états de plateau suivant.



Tout à fait d'accord.

Suivant un tradeoff mémoire/cpu, le plateau sera composé de pointeurs
sur les pièces ou un tableau de char identifiant la pièce à une
position (donc un switch case).



On a la dualité Position/pièce : une pièce peut contenir sa position,
une position peut contenir la pièce à cette position. Il faut choisir,
ou avoir les 2 (la redondance), pour des raisons de performance ou
d'algorithme de recherche des coups... (je ne suis pas spécialiste des
échecs).

Si cela pose des problèmes de performances, alors il ne faut pas
utiliser C++ mais C, par exemple, avec force enums et switches mais ça
va être non maintenable. Cependant on sait que si c'est la performance
qui prime sur tous les autres facteurs, le code résultant n'est, en
général, plus maintenable.



Et peut être aussi pas plus rapide.



Qui sait...