Suivez-nous sur X
|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
Programmation : C/C++ - Exec et la programmation orienté objet : les fonctions virtuelles du C++
(Article écrit par Gilles Dridi et extrait d'Amiga News - novembre 1996)
|
|
Voici une nouvelle série d'articles sur le système d'exploitation : Exec et la programmation orientée objet. J'ai débuté sur
Amiga avec l'environnement Devpac : assembleur et débogueur, et non avec le compilateur C que distribuait SAS, à l'époque. Aussi
je vous conseille de vous procurer un compilateur C (du domaine public) si vous commencez à programmer, car vous aurez à la
fois un compilateur et un assembleur (mais vous pouvez aussi vous intéresser à d'autres langages comme ceux présentés dans
Amiga News). Comme prérequis, il faut déjà connaître la syntaxe du C et savoir faire des éditions de liens et pour cela, je
vous renvoie aux articles déjà parus dans Amiga News. Idem, vous devez aussi connaître la signification des mnémoniques du
langage machine (de notre Amiga) telles que : JMP, BSR, RTS, etc. et des différents modes d'adressage.
Toutefois, du langage BASIC au Smalltalk ou du µP 6502 au PowerPC, les techniques de base ou concepts des langages ou architectures
machines sont récurrents et vous ne perdrez jamais votre temps à apprendre l'un plutôt que l'autre.
Le premier article traite des fonctions virtuelles de C++ v1.0 (sans héritage multiple, ni exceptions, ni templates). Après cette
introduction sur un des mécanismes importants de C++, nous verrons comment construire un
module-bibliothèque.
Les fonctions virtuelles avec C/C++
Principe
C'est une indirection. L'adresse de la fonction est prise dans un tableau au moment de l'exécution. Dans un autre langage à objets,
on appelle ce mécanisme : lien dynamique. Exemple : pour détailler ce mécanisme, j'ai choisi une classe B qui hérite d'une classe
A. Voici le schéma d'héritage :
___________
/ Classe A
---------------
|affo() |
|virt. aff() |
---------------
/|
|
_____|______
/ Classe B
----------------
|affo() |
|virt. aff() |
---------------
|
La fonction affo() n'est pas virtuelle, aff() l'est.
Interface
class A {
protected:
char lettre;
public:
A(char c='A');
void affo();
virtual void toto();
virtual void aff();
};
class B: public A {
public:
B();
void affo();
void toto();
void aff();
};
|
Implantation
A::A(char c):lettre(c) { printf("A::A(%c)\n", lettre); }
void A::affo() { printf("A::affo()\n"); }
void A::toto() {; }
void A::aff() { printf("A::aff()\n"); }
B::B(): ('B') { printf("B::B(%c)\n", lettre); }
void B::affo() { printf("B::affo()\n"); }
void B::toto() {; }
void B::aff() { printf("B::aff()\n"); }
|
Résultat du programme de test.
void main() {
A a, *ptrA;
B b;
a.aff();
b.aff();
ptrA= &b;
ptrA->affo();
ptrA->aff();
}
A::A(A)
A::A(B)
B::B(B)
A::aff()
B::aff()
A::affo()
B::aff()
|
Alors que le pointeur sur la classe A (ptrA) fait un appel à la fonction affo() de la classe A, l'appel de aff() qui est une
fonction virtuelle avec ce pointeur sur la classe A fait un appel à la fonction aff() de la classe B. Que s'est-il passé ?
Explications
Voici le code C généré pour la classe contenant au moins une fonction virtuelle :
struct A {
char _A_lettre ;
int (**_A__vptr )();
} ;
int _A_toto ();
int _A_aff ();
static int (*A__vtbl[])() = {
(int(*)()) _A_toto ,
(int(*)()) _A_aff , 0};
|
La classe A du C++ est traduite en C, en structure or en plus de notre caractère, on a un pointeur sur un pointeur de fonction
ou un pointeur sur tableau de fonction (_A__vptr). Puis, la déclaration d'un tableau de fonctions "virtuelles" avec son
pointeur _A__vtbl.
struct A *_A__ctor (); // interface de la fonction
struct A *_A__ctor (_auto_this , _auto_c )
// c'est proche du langage machine...
register struct A *_auto_this ;
char _auto_c ;
{
if (_auto_this == 0 )_auto_this = (struct A *)_new ( (long )6) ;
( (_auto_this -> _A__vptr = A__vtbl), _auto_this ->_A_lettre = _auto_c ) ;
printf ( "A::A(%c)\n", _auto_this -> _A_lettre ) ;
return _auto_this ;
}
|
En C++, le constructeur, noté __ctor ici, se charge de réserver la mémoire pour la structure que l'on vient de voir et
d'initialiser dans notre cas le pointeur _A__vptr sur la table des fonctions virtuelles.
Passons à la classe B.
struct B {
char _A_lettre ;
int (**_A__vptr )();
} ;
int _B_toto ();
int _B_aff ();
static int (*B__vtbl[])() = {
(int(*)()) _B_toto ,
(int(*)()) _B_aff , 0};
|
La classe B du C++ est traduite en C, en structure qui contient les mêmes items que la structure A (Classe A). La classe B n'a
rien ajouté en effet. Par contre, on a une nouvelle déclaration du tableau des fonctions virtuelles. Celui-ci est initialisé
avec les fonctions virtuelles de la classe B.
struct B *_B__ctor ();
struct B *_B__ctor (_auto_this )
register struct B *_auto_this ;
{
if (_auto_this == 0 )_auto_this = (struct B *)_new ( (long )6) ;
( (_auto_this = (struct B *)_A__ctor ( ((struct A *)_ auto_this ), 'B' ) ), _auto
_this ->_A__vptr = B__vtbl) ;
printf ( "B::B(%c)\n", _auto_this -> _A_lettre ) ;
return _auto_this ;
}
|
Qu'est-ce qui change dans le constructeur de la classe B ? L'affectation du pointeur _A__vptr par le nouveau tableau.
Conclusion
Rappel du programme de test :
void main() {
A a, *ptrA;
B b;
a.aff();
b.aff();
ptrA= &b;
ptrA->affo();
ptrA->aff();
}
Résultat:
A::A(A)
A::A(B)
B::B(B)
A::aff()
B::aff()
A::affo()
B::aff()
|
Enfin, voyons ce qui s'est passé dans le programme de test lors de l'appel ptrA->aff() ;
int main (){ _ConStrucT(); {
struct A _auto_a ;
struct A *_auto_ptrA ;
struct B _auto_b ;
_A__ctor ( & _auto_a , 'A' ) ;
_B__ctor ( & _auto_b ) ;
(*(((int (*)())((& _auto_a )-> _A__vptr [1]))))( & _auto_a ) ;
(*(((int (*)())((& _auto_b )-> _A__vptr [1]))))( & _auto_b ) ;
_auto_ptrA = (struct A *)(& _auto_b );
_A_affo ( _auto_ptrA ) ;
(*(((int (*)())(_auto_ptrA -> _A__vptr [1]))))( _auto_ptrA ) ;
}
}
|
Déclarations des classes A et B (structures dans ce code C), appels des constructeurs, appels des fonctions virtuelles ;
le premier appel _A__vptr est initialisé avec le tableau _A__vtbl[], le deuxième appel _A__vptr est initialisé avec le tableau
_B_vtbl[] qui contient B::aff(). Cette initialisation est faite par l'appel du constructeur de la classe B au-dessus :
_B__ctor( & _auto_b );
Ensuite, notre pointeur sur la classe A est initialisé avec l'adresse de la structure B (de la classe B). L'appel à affo() via
ce pointeur est traduit en C par l'appel à la fonction affo() alors que l'appel à aff() via ce pointeur est traduit par le
même appel que b.aff(). En effet, le _auto_ptrA pointe sur & _auto_b. Voir le paragraphe ci-dessus.
On peut donc dire qu'il y a indirection ou lien dynamique pour les fonctions déclarées virtuelles en C++ et on retiendra que
l'initialisation de cette indirection se fait au moment de l'appel de son constructeur.
Exemples
Voici deux programmes C et son équivalent C++ qui réalisent la même sortie écran :
Exemple C1 :
#define MAXNOM 3 // problème garder la table et les types à jour!
char * Noms[MAXNOM]= { "Fruit", "Pomme", "Abricot" };
enum Type { TFruit= 0, TPomme, TAbricot };
struct Fruit { struct Fruit * suivant; enum Type type; };
char * ident(struct Fruit * p) { return Noms[p->type]; }
void main() {
struct Fruit fruit = { 0, TFruit };
struct Fruit pomme = { 0, TPomme };
struct Fruit abricot = { 0, TAbricot };
struct Fruit * ptrF= &fruit;
fruit.suivant= &pomme; pomme.suivant= &abricot;
while(ptrF) { printf("%s\n", ident(ptrF)); ptrF= ptrF->suivant; }
}
|
Exemple C2 :
struct Fruit { struct Fruit * suivant; char * (*ident)(); };
char * identFruit() { return "Fruit"; }
char * identPomme() { return "Pomme"; }
char * identAbricot() { return "Abricot"; }
void main() {
struct Fruit fruit = { 0, &identFruit };
struct Fruit pomme = { 0, &identPomme };
struct Fruit abricot = { 0, &identAbricot };
struct Fruit * ptrF= &fruit;
fruit.suivant= &pomme; pomme.suivant= &abricot;
while(ptrF) { printf("%s\n", (ptrF->ident)()); ptrF= ptrF->suivant; }
}
Exemple C++:
class Fruit {
Fruit * suivant;
public:
Fruit(Fruit * sui) { suivant= sui; }
Fruit * le_suivant() { return suivant; }
virtual char * ident() { return "Fruit"; }
};
class Pomme: public Fruit {
public:
Pomme(Fruit * sui): (sui) {}
char * ident() { return "Pomme"; }
};
class Abricot: public Fruit {
public:
Abricot(Fruit * sui): (sui) {}
char * ident() { return "Abricot"; }
};
void main() {
Abricot abricot(0);
Pomme pomme(&abricot);
Fruit fruit(&pomme), *ptrF= &fruit;
while(ptrF) { printf("%s\n", ptrF->ident()); ptrF= ptrF->le_suivant(); }
}
|
Voilà, c'est fini, à bientôt.
Soutenez le travail de l'auteur
|
|
|