Appels de méthodes virtuelles dans les constructeurs des classes de base

De RAD Studio
Aller à : navigation, rechercher

Remonter à Modèles C++ et Pascal Objet


Les méthodes virtuelles invoquées depuis le corps des constructeurs des classes de base de style Delphi, c'est-à-dire les classes implémentées en Pascal Objet, sont réparties comme en C++, selon le type à l'exécution de l'objet. Comme C++Builder combine le modèle Pascal Objet d’affectation immédiat du type d’exécution d’un objet avec le modèle C++ de construction des classes de base avant la construction de la classe dérivée, l’appel des méthodes virtuelles dans les constructeurs des classes de base dans les classes de style Delphi peut induire des effets secondaires indésirables. Ces effets sont décrits plus loin et illustrés par un exemple d’une classe instanciée dérivée d’au moins une classe de base. Dans cette discussion, la classe instanciée est désignée comme étant la classe dérivée.

Modèle Pascal Objet

En Pascal Objet, les programmeurs peuvent utiliser le mot clé inherited qui leur permet d’appeler le constructeur de la classe de base n’importe où dans le corps du constructeur d’une classe dérivée. En conséquence, si la classe dérivée redéfinit une méthode virtuelle dépendant de la configuration de l’objet ou de l’initialisation de données membres, cela peut avoir lieu avant l’appel du constructeur de la classe de base et l’appel des méthodes virtuelles.


Modèle C++

La syntaxe C++ ne dispose pas du mot clé inherited pour appeler le constructeur de la classe de base lors de la construction de la classe dérivée. Dans le modèle C++, l’utilisation de inherited est inutile puisque le type à l’exécution de l’objet est celui de la classe en cours de construction et pas celui de la classe dérivée. Donc, les méthodes virtuelles appelées sont celles de la classe en cours et pas celles de la classe dérivée. De ce fait, il n’est pas nécessaire d’initialiser les données membres ou de configurer l’objet de la classe dérivée avant d’appeler ces méthodes.


Modèle C++Builder

En C++Builder, les objets de style Delphi ont le type à l’exécution de la classe dérivée durant les appels aux constructeurs de classe de base. Donc, si le constructeur de la classe de base appelle une méthode virtuelle, la méthode de la classe dérivée est appelée si elle a été redéfinie par la classe dérivée. Si la méthode virtuelle dépend de la liste d’initialisation ou du corps du constructeur de la classe dérivée, la méthode est appelée avant que cela ne se produise. Par exemple, CreateParams est une fonction membre virtuelle qui est appelée indirectement dans le constructeur de TWidgetControl. Si vous dérivez une classe de TWidgetControl et redéfinissez CreateParams afin qu’elle dépende de quelque chose effectué dans votre constructeur, ce code est traité après l’appel de CreateParams. Cette situation est valable pour toutes les classes dérivées d’une base. Considérons une classe C dérivée de B, elle-même dérivée de A. Quand une instance C est créée, A doit également appeler la méthode redéfinie de B si B redéfinit la méthode alors que C ne le fait pas.

Il faut faire attention aux méthodes virtuelles comme CreateParams qui ne sont pas appelées directement dans les constructeurs mais qui le sont indirectement.

Exemple : appel de méthodes virtuelles

L’exemple suivant compare des classes C++ et de style Delphi qui redéfinissent des méthodes virtuelles. Cet exemple illustre la manière dont les appels à ces méthodes dans les constructeurs des classes de base sont résolus dans les deux cas. MyBase et MyDerived sont des classes C++ standard. MyRTLBase et MyRTLDerived sont des classes de style Delphi descendant de TObject. La méthode virtuelle what_am_I() est redéfinie dans les deux classes dérivées mais elle est appelée uniquement dans le constructeur de la classe de base et pas dans le constructeur de la classe dérivée.

# include  <sysutils.hpp >
# include  <iostream.h >
# include  <stdio.h>

// non-Delphi style classes
class MyBase {
public:

	MyBase() {
		what_am_I();
	}

	virtual void what_am_I() {
		cout << "I am a base" << endl;
	}
};

class MyDerived : public MyBase {
public:

	virtual void what_am_I() {
		cout << "I am a derived" << endl;
	}

};

// Delphi style classes
class MyRTLBase : public TObject {
public:

	__fastcall MyRTLBase() {
		what_am_I();
	}

	virtual void __fastcall what_am_I() {
		cout << "I am a base" << endl;
	}
};

class MyRTLDerived : public MyRTLBase {
public:
	virtual void __fastcall what_am_I() {
		cout << "I am a derived" << endl;
	}
};

int main(void) {
	MyDerived d; // instantiation of the C++ class
	MyRTLDerived *pvd = new MyRTLDerived; // instantiation of the Delphi style class
	getchar();
	return 0;

}

Le résultat de cet exemple est :

I am a base
I am a derived

Il est dû à la différence des types d'exécution de MyDerived et de MyRTLDerived lors de l’appel à leur constructeur respectif de classe de base.

Initialisation par le constructeur des données membres pour les fonctions virtuelles

Comme les données membres peuvent être utilisées dans les fonctions virtuelles, il est important de savoir quand et comment elles sont initialisées. En Pascal Objet, toutes les données non initialisées, le sont avec des zéros. Cela s’applique, par exemple, aux classes de base dont les constructeurs ne sont pas appelés avec inherited. En C++ standard, la valeur des données membres non initialisées est indéterminée. Les types suivants de données membres de classe doivent être initialisés dans la liste d’initialisation du constructeur de la classe :

  • Références
  • Les données membres n’ayant pas de constructeur par défaut.


Sinon, la valeur de ces données membres ou de celles initialisées dans le corps du constructeur est non définie quand les constructeurs de classe de base sont appelés. En C++Builder, la mémoire des classes de style Delphi est initialisée à zéro.

Une fonction virtuelle dépendant de la valeur de variables membres initialisées dans le corps du constructeur ou dans la liste d’initialisation peut se comporter comme si les variables étaient initialisées à zéro. En effet, le constructeur de la classe de base est appelé avant le traitement de la liste d’initialisation ou l’entrée dans le corps du constructeur. Cela est illustré par l’exemple suivant :

 # include  <sysutils.hpp>

class Base : public TObject {

public:
	__fastcall Base() {
		init();
	}

	virtual void __fastcall init() {
	}

};

class Derived : public Base {

public:
	Derived(int nz) : not_zero(nz) {
	}

	virtual void __fastcall init() {

		if (not_zero == 0)
			throw Exception("not_zero is zero!");
	}

private:
	int not_zero;
};

int main(void)

{
	Derived *d42 = new Derived(42);
	return 0;

}

Cet exemple déclenche une exception dans le constructeur de Base. Comme Base est construite avant Derived, not_zero n’a pas encore été initialisé avec la valeur 42 transmise au constructeur. Attention : vous ne pouvez pas initialiser les données membres des classes de style Delphi avant l’appel des constructeurs de classe de base.

Voir aussi