OVH Cloud OVH Cloud

utilisation du mot clef "extern"

7 réponses
Avatar
Ahryman40k
Bonjour,
j'aimerai savoir si j'ai bien compris le fonctionnement du mot clef "extern"
dans ce cas particulier.

soit une classe CToto // bein oui il fait tout toto

class CToto
{
public;
CToto();
~CToto();
};

extern CToto mToto;


si dans un autre fichier je declare une autre classe

extern CToto* pToto;

Class A
{
A();
~A();

void test()
{
ASSERT ( pToto != NULL );
}
};

est ce que Toto ets NULL ou non ??
Toto est il créé au chargement du programme ou pas ??

d'apres ce que j'ai compris, n'importe ou ou je declare cet "extern CToto*
pToto" , je peux acceder aux proprietes et methodes d'une instance globale
et unique de CToto ??

Qui se charge de la construction et de la destruction ??
Car je suis tombé sur des cas ou le developpeur fait un new CToto dans sa
classe malgre la declaration en extern, il se charge egalement de sa
destruction.

Je suis pas contre une complete reexplication claire et concise !! ;))
Car je crois que j'ai un beau fouillit dans le crane !!

merci.

7 réponses

Avatar
Christophe Lephay
Ahryman40k wrote:
Bonjour,
j'aimerai savoir si j'ai bien compris le fonctionnement du mot clef
"extern" dans ce cas particulier.
extern CToto* pToto;


Extern déclare une variable globale, qu'il faut définir par ailleurs (sans
le extern) sans quoi tu as une erreur à l'édition de lien.

Par exemple dans toto.h :

extern CToto* pToto;

et dans toto.cpp :

CToto* pToto = NULL;

Class A
{
A();
~A();

void test()
{
ASSERT ( pToto != NULL );
}
};

est ce que Toto ets NULL ou non ??
Toto est il créé au chargement du programme ou pas ??


Toto est créé automatiquement au chargement du programme avant la fonction
main(). Par contre, dans ton code, tu n'as pas la garantie que pToto soit
initialisé tant que tu n'es pas dans la fonction main(), ce qui est le cas
si tu crées d'autres objets globaux...

d'apres ce que j'ai compris, n'importe ou ou je declare cet "extern
CToto* pToto" , je peux acceder aux proprietes et methodes d'une
instance globale et unique de CToto ??


Oui, mais elle doit être définie dans quelque part...

Qui se charge de la construction et de la destruction ??


Dans le cas d'un objet global, c'est le compilateur qui s'en charge, comme
pour les objets auto, du reste, si ce n'est que leur durée de vie n'est pas
locale à une fonction (construit avant main(), et détruit juste après).

Car je suis tombé sur des cas ou le developpeur fait un new CToto
dans sa classe malgre la declaration en extern, il se charge
egalement de sa destruction.


C'est une autre question. Un pointeur CToto * ne créant pas d'objet CToto,
il faut bien faire un new si tu veux créer un objet et le faire pointer
dessus, et il faut le supprimer par ailleurs, avec delete...

Je suis pas contre une complete reexplication claire et concise !! ;))
Car je crois que j'ai un beau fouillit dans le crane !!


Il y a confusion entre deux notions distinctes, à savoir les pointeurs, qui
ne sont pas des objets, et les variables globales, qui peuvent être des
objets comme des pointeurs, qu'il conviendra alors de faire pointer sur un
objet valide, global ou pas...

Chris

Avatar
Ahryman40k
merci pour ta reponse,
quelques petits exemples simple m'ont permis de m'eclairer l'esprit a ce
sujet, et le code fonctionne comme je le voulais !!

merci, encore ;))


"Christophe Lephay" a écrit dans le message
de news: bpfjop$262$
Ahryman40k wrote:
Bonjour,
j'aimerai savoir si j'ai bien compris le fonctionnement du mot clef
"extern" dans ce cas particulier.
extern CToto* pToto;


Extern déclare une variable globale, qu'il faut définir par ailleurs (sans
le extern) sans quoi tu as une erreur à l'édition de lien.

Par exemple dans toto.h :

extern CToto* pToto;

et dans toto.cpp :

CToto* pToto = NULL;

Class A
{
A();
~A();

void test()
{
ASSERT ( pToto != NULL );
}
};

est ce que Toto ets NULL ou non ??
Toto est il créé au chargement du programme ou pas ??


Toto est créé automatiquement au chargement du programme avant la fonction
main(). Par contre, dans ton code, tu n'as pas la garantie que pToto soit
initialisé tant que tu n'es pas dans la fonction main(), ce qui est le cas
si tu crées d'autres objets globaux...

d'apres ce que j'ai compris, n'importe ou ou je declare cet "extern
CToto* pToto" , je peux acceder aux proprietes et methodes d'une
instance globale et unique de CToto ??


Oui, mais elle doit être définie dans quelque part...

Qui se charge de la construction et de la destruction ??


Dans le cas d'un objet global, c'est le compilateur qui s'en charge, comme
pour les objets auto, du reste, si ce n'est que leur durée de vie n'est
pas

locale à une fonction (construit avant main(), et détruit juste après).

Car je suis tombé sur des cas ou le developpeur fait un new CToto
dans sa classe malgre la declaration en extern, il se charge
egalement de sa destruction.


C'est une autre question. Un pointeur CToto * ne créant pas d'objet CToto,
il faut bien faire un new si tu veux créer un objet et le faire pointer
dessus, et il faut le supprimer par ailleurs, avec delete...

Je suis pas contre une complete reexplication claire et concise !! ;))
Car je crois que j'ai un beau fouillit dans le crane !!


Il y a confusion entre deux notions distinctes, à savoir les pointeurs,
qui

ne sont pas des objets, et les variables globales, qui peuvent être des
objets comme des pointeurs, qu'il conviendra alors de faire pointer sur un
objet valide, global ou pas...

Chris





Avatar
kanze
"Christophe Lephay" wrote in message
news:<bpfjop$262$...
Ahryman40k wrote:

j'aimerai savoir si j'ai bien compris le fonctionnement du mot clef
"extern" dans ce cas particulier.
extern CToto* pToto;


Extern déclare une variable globale, qu'il faut définir par ailleurs
(sans le extern) sans quoi tu as une erreur à l'édition de lien.


Ça serait trop simple. Ce n'est vrai que si l'extern n'a pas
d'initialisateur. Donc :

extern CToto* pToto ; // declarateur...
extern CToto* pToto = NULL ; // définition...

Par exemple dans toto.h :

extern CToto* pToto;

et dans toto.cpp :

CToto* pToto = NULL;


De point de vue de style, je suis d'accord. Mais strictement parlant,
l'initialisation n'est pas nécessaire. L'initialisation d'un objet à
durée de vie statique se fait (conceptuellement) en trois phases : une
initialisation à zéro, l'initialisation statique proprement dite, et
l'initialisation dynamique. L'initialisation à zéro d'un type de base,
d'un pointer ou d'un type enum, c'est de lui donner la valeur qu'il
aurait si on lui affectait 0. L'initialisation à zéro d'un type de
classe autre qu'une union, c'est l'initialisation à zéro de toutes ces
classes de base et de tous ses membres. L'initialisation à zéro d'une
union, c'est l'initialisation à zéro du premier élément.

Mais j'avoue que je le trouve plus clair quand on écrit l'initialisation
explicitement, même si ce n'est pas nécessaires.

Class A
{
A();
~A();

void test()
{
ASSERT ( pToto != NULL );
}
};

est ce que Toto ets NULL ou non ??
Toto est il créé au chargement du programme ou pas ??


Toto est créé automatiquement au chargement du programme avant la
fonction main(). Par contre, dans ton code, tu n'as pas la garantie
que pToto soit initialisé tant que tu n'es pas dans la fonction
main(), ce qui est le cas si tu crées d'autres objets globaux...


Il y a bien une garantie que les initialisations à zéro et les
initialisations statiques précède toute initialisation dynamique. On
peut donc y compter dans un constructeur, même dans un constructeur d'un
objet statique.

d'apres ce que j'ai compris, n'importe ou ou je declare cet "extern
CToto* pToto" , je peux acceder aux proprietes et methodes d'une
instance globale et unique de CToto ??


Oui, mais elle doit être définie dans quelque part...

Qui se charge de la construction et de la destruction ??


Dans le cas d'un objet global, c'est le compilateur qui s'en charge,
comme pour les objets auto, du reste, si ce n'est que leur durée de
vie n'est pas locale à une fonction (construit avant main(), et
détruit juste après).

Car je suis tombé sur des cas ou le developpeur fait un new CToto
dans sa classe malgre la declaration en extern, il se charge
egalement de sa destruction.


C'est une autre question. Un pointeur CToto * ne créant pas d'objet
CToto, il faut bien faire un new si tu veux créer un objet et le faire
pointer dessus, et il faut le supprimer par ailleurs, avec delete...


S'il n'y a qu'un seul objet, on pourrait envisager à ne pas le
detruire. Ce n'est pas très propre, mais ça pourrait éviter des
problèmes de l'ordre de destruction.

Je suis pas contre une complete reexplication claire et concise !!
;)) Car je crois que j'ai un beau fouillit dans le crane !!


Il y a confusion entre deux notions distinctes, à savoir les
pointeurs, qui ne sont pas des objets, et les variables globales, qui
peuvent être des objets comme des pointeurs, qu'il conviendra alors de
faire pointer sur un objet valide, global ou pas...


Il y a vraiment confusion:-). Justement, les pointeurs sont des objets.
À part entière. Mais CToto*, c'est un pointeur, et non un CToto. Son
initialisation est donc celle d'un pointeur, c-à-d par défaut zéro quand
il a la durée de vie statique, et indéfini autrement.

Rien n'empêche d'écrire quelque chose du genre :

CToto* pToto = new CToto ;

Mais attention : dans ce cas-là, c'est une initialisation dynamique,
dont l'ordre n'est plus garantie : dans d'autres initialisations
dynamiques, on peut voir pToto initialisé avec le new, mais on peut
aussi le voir initialisé à zéro, c-à-d NULL.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16


Avatar
amerio
Rien n'empêche d'écrire quelque chose du genre :

CToto* pToto = new CToto ;

Mais attention : dans ce cas-là, c'est une initialisation dynamique,
dont l'ordre n'est plus garantie : dans d'autres initialisations
dynamiques, on peut voir pToto initialisé avec le new, mais on peut
aussi le voir initialisé à zéro, c-à-d NULL.


Je ne suis pas sur de comprendre, et je voudrais etre sur, là.
Tu veux dire que si on fait ca :

// dans toto.h
class CToto {..};
extern CToto *pToto;

// dans toto.cpp
CToto* pToto = new CToto;

// dans main.cpp
#include "toto.h"
int main()
{
asssert(pToto!=NULL); // !!!
}

Tu veux dire que selon plateforme/compilo/direction du vent, l'assert peut se déclencher
????

Avatar
Christophe Lephay
amerio wrote:
Rien n'empêche d'écrire quelque chose du genre :

CToto* pToto = new CToto ;

Mais attention : dans ce cas-là, c'est une initialisation dynamique,
dont l'ordre n'est plus garantie : dans d'autres initialisations
dynamiques, on peut voir pToto initialisé avec le new, mais on peut
aussi le voir initialisé à zéro, c-à-d NULL.


Je ne suis pas sur de comprendre, et je voudrais etre sur, là.
Tu veux dire que si on fait ca :

// dans toto.h
class CToto {..};
extern CToto *pToto;

// dans toto.cpp
CToto* pToto = new CToto;

// dans main.cpp
#include "toto.h"
int main()
{
asssert(pToto!=NULL); // !!!
}

Tu veux dire que selon plateforme/compilo/direction du vent, l'assert
peut se déclencher ????


Le problème ne se pose pas dès lors que tu es dans main. Si j'ai bien
compris les propos de James :

struct CTotoUser
{
CTotoUser( CToto * pToto ) {
assert( pToto!=NULL); }
};

CToto * pToto = new CToto;
CTotoUser TotoUser( pToto );

Bien que pToto soit avant TotoUser, il ne sera initialisé qu'après, car is
s'agit d'une initialisation dynamique pour le premier, et statique pour le
second...

Chris


Avatar
kanze
"amerio" wrote in message
news:<dEZub.18577$...

Rien n'empêche d'écrire quelque chose du genre :

CToto* pToto = new CToto ;

Mais attention : dans ce cas-là, c'est une initialisation dynamique,
dont l'ordre n'est plus garantie : dans d'autres initialisations
dynamiques, on peut voir pToto initialisé avec le new, mais on peut
aussi le voir initialisé à zéro, c-à-d NULL.


Je ne suis pas sur de comprendre, et je voudrais etre sur, là. Tu
veux dire que si on fait ca :

// dans toto.h
class CToto {..};
extern CToto *pToto;

// dans toto.cpp
CToto* pToto = new CToto;

// dans main.cpp
#include "toto.h"
int main()
{
asssert(pToto!=NULL); // !!!
}

Tu veux dire que selon plateforme/compilo/direction du vent, l'assert
peut se déclencher ????


Non. Ce que je veux dire, c'est :

- Il y a trois phases d'initialisation :

1. L'initialisation à zéro. Sur des machines courantes, ça revient
en fait à faire un memset de toute la mémoire à zéro. C'est
défini d'une façon différente dans la norme pour prendre en
compte les cas où 0.0 ou des pointeurs nuls n'ont pas tous les
bits à zéro.

2. L'initialisation statique. En gros, l'initialisation par des
constantes, connues du compilateur.

3. L'initialisation dynamique. L'initialisation exige l'execution
du code au démarrage du programme.

- Dans la pratique, les deux premières phases ont lieu lors du
chargement du programme, la première par un memset, et la séconde
par une copie des secteurs disques dans la mémoire.

- Dans la pratique, au moins dans les cas des éditions de liens
pûrement statiques, l'initialisation dynamique s'execute avant
d'entrer dans main. À l'intérieur d'une module, les initialisations
ont lieu dans l'ordre lexique des déclarations. Entre les modules,
en revanche, l'ordre n'est pas défini.

Ce dernier point signifie que si le code d'une initialisation dynamique
accède à une variable statique initialisée elle aussi dynamiquement dans
une autre module, il ne sait pas si l'initialisation dynamique de cette
autre variable a eu lieu ou non. Dans des cas comme ci-dessus, en
revanche, il peut le determiner dynamiquement, en comparant à NULL --
l'initialisation à zéro a toujours lieu avant toute initialisation
dynamique.

En passant, j'utilise cette technique pour s'assurer de l'initialisation
des singletons avant le démarrage des threads. En gros, je sais que dans
mes applications, les threads ne sont jamais démarré avant l'entrée en
main. J'écris donc :

Singleton* Singleton::ourInstance = Singleton::createInstance() ;

Singleton*
Singleton::createInstance()
{
return ourInstance == NULL
? new Singleton
: ourInstance ;
}

Singleton&
Singleton::instance()
{
if ( ourInstance == NULL ) {
ourInstance = createInstance() ;
}
return *ourInstance ;
}

Dans les initialisations statiques, il est possible que
Singleton::instance soit appelé avant l'initialisation de
ourInstance. Si ça arrive, il initialise bien ourInstance comme il faut,
comme dans un singleton classique. Mais l'initialisation dynamique de
ourInstance assure que une fois entré dans main, la valeur de
ourInstance n'est jamais modifiée. Je peux donc y accéder tranquillement
sans un lock. (Le démarrage d'un thread assure la visibilité de la
valeur courant dans le thread démarré, au moins sous Posix.)

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16


Avatar
kanze
"Christophe Lephay" wrote in message
news:<bpi6d2$orr$...
amerio wrote:
Rien n'empêche d'écrire quelque chose du genre :

CToto* pToto = new CToto ;

Mais attention : dans ce cas-là, c'est une initialisation
dynamique, dont l'ordre n'est plus garantie : dans d'autres
initialisations dynamiques, on peut voir pToto initialisé avec le
new, mais on peut aussi le voir initialisé à zéro, c-à-d NULL.


Je ne suis pas sur de comprendre, et je voudrais etre sur, là. Tu
veux dire que si on fait ca :

// dans toto.h
class CToto {..};
extern CToto *pToto;

// dans toto.cpp
CToto* pToto = new CToto;

// dans main.cpp
#include "toto.h"
int main()
{
asssert(pToto!=NULL); // !!!
}

Tu veux dire que selon plateforme/compilo/direction du vent,
l'assert peut se déclencher ????


Le problème ne se pose pas dès lors que tu es dans main. Si j'ai bien
compris les propos de James :

struct CTotoUser
{
CTotoUser( CToto * pToto ) {
assert( pToto!=NULL); }
};

CToto * pToto = new CToto;
CTotoUser TotoUser( pToto );

Bien que pToto soit avant TotoUser, il ne sera initialisé qu'après,
car is s'agit d'une initialisation dynamique pour le premier, et
statique pour le second...


Sauf tque CTotoUser a aussi une initialisation dynamique. On a une
initialisation dynamique ou si la valeur d'initialisation n'est pas une
constante (dans le sens du langage), OU si le type en question a un
constructeur non-trivial.

Un petit exercise intéressant, c'est de comparer les sorties du
programme suivant selon que STATIC est défini ou non :

#include <iostream>
#include <ostream>

int* f() ;
int* g() ;

int i = 42 ;
int* const p1 = f() ;
int* const p2
#ifdef STATIC
= &i ;
#else
= g() ;
#endif
int* const p3 = f() ;

int
main()
{
std::cout << p1 << 'n' ;
std::cout << p2 << 'n' ;
std::cout << p3 << 'n' ;
return 0 ;
}

int*
f()
{
return p2 ;
}

int*
g()
{
return new int( 42 ) ;
}

Dans ce cas-ci, l'ordre d'initialisation est bien défini (afin que je
sois sûr que le programme manifeste le symptome que je veux montrer).
Dans la plupart des applications réeles, en revanche, on aura des
variables dans des unités de traduction différentes, et donc, avec un
ordre d'initialisation indéterminée. Si c'est le cas de p1 et de p2,
ci-dessus (avec STATIC non défini), par exemple, l'initialisation de p2
serait aussi indéterminée -- peut-être 0, peut-être le pointeur valid,
selon les aléas de l'édition de liens ou d'autres choses.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16