Il est né ?
Vous êtes ici : www.xgarreau.org >> aide >> devel >> cpp : Classes abstraites et enfants cachés (g++ - partie 5)
Version imprimable

Classes abstraites et enfants cachés (g++ - partie 5)

Je vous ai jusqu'à maintenant présenté les bases du C++, nous allons pouvoir à présent nous intéresser à des domaines plus précis tels que l'héritage multiple, les fonctions virtuelles et les classes abstraites cette fois-ci. En fin d'article nous verrons les formes d'héritage non publiques, rarement utilisées mais disponibles, protected et private.

Héritage multiple

L'héritage nous permet d'étendre une classe A en lui ajoutant des fonctionnalités supplémentaires pour créer une classe F. C'est bien mais admettons que les fonctionnalités existent déjà dans un autre classe existante, appelons la B. Comment choisir de quelle classe la classe F doit elle hériter pour avoir les qualités des deux ? Afin de ne pas avoir à se poser ce genre de questions, le C++ propose d'utiliser l'héritage multiple, qui permet à F d'hériter de A et de B. Cela se note de la façon suivante :

/* ex_5_1.c++ */
#include <iostream>
using namespace std;

class A {
protected:
	int a;
public:
	A() { cout << "A : " << a << endl; };
	void fait_a() { cout << "a" << endl; }
};

class B {
protected:
	int b;
public:
	B() { cout << "B : " << b << endl; };
	void fait_b() { cout << "b" << endl; }
};

class F : public A, public B {
public:
	F();
};

F::F() {
	cout << "F : " << a << " " << b << endl;
}

int main (void) {
	F f;
	f.fait_a();
	f.fait_b();
	return 0;
}

On compile et on exécute :

$ g++ -o ex_5_1 ex_5_1.c++ 
[xavier@zaz2 5]$ ./ex_5_1 
A : 134518812
B : 134519356
F : 134518812 134519356
a
b

Pas de grosse surprise dans le code si vous avez suivi jusqu'ici ... La classe F est dérivée de A et B. Il s'agit d'héritage public comme le montre le mot clé public placé avant le nom des classes parentes (voir fin d'article).

Cet exemple montre la création des instances de A et B et la récupération de leurs membres protégés pour l'instanciation de F. On voit également l'utilisation des méthodes publiques fait_a et fait_b de a et b par l'intermédiaire de l'objet f. L'héritage multiple, c'est aussi simple que celà.

Dans la pratique, l'héritage multiple peut servir, comme ici, à créer de nouvelles classes en en combinant plusieurs, on peut d'ailleurs en combiner plus de deux soit dit en passant, mais la vraie puissance de l'héritage multiple est libérée lorsqu'on la combine à la notion de fonction virtuelle, ce que nous allons voir tout de suite.

Fonctions virtuelles

Reprenons nos personnes, travailleurs et directeurs qui nous suivent depuis quelques articles (Je rappelle pour ceux qui n'ont pas suivi le début de la série C++ que les sources des exemples se trouvent sur mon site, dont l'url est donnée en fin d'article). On va placer 5 personnes, 4 travailleurs et une directrice dans un bus et leur demander de se présenter. Nous allons pour celà remplir une pile (stack, classe de la STL vue lors du premier article de la série), puis la vider. Lors de chaque "descente" du bus, la personne se présente :

/* ex_5_2.c++ */
#include <iostream>
#include <cstdlib>
#include <stack>
#include <personne.h>
#include <directeur.h>
#include <travailleur.h>
using namespace std;

int main (void) {
	srand(time(NULL));
	stack<personne*> bus;
	int place;

//	Montée dans le bus
	for (place=0; place<5; place++) {
		bus.push(new personne("X1", "Y1", 27));
	}
	for (place=0; place<4; place++) {
		bus.push(new travailleur("X2", "Y2", 27, SURPRISE, "Boulot quelconque"));
	}
	bus.push(new directeur("Foobar", "Toto", 45, FEMME));

//	Descente
	for (place=0; place<MAX_PLACES; place++) {
		personne* p = bus.top();
		cout << p->get_identification() << endl;
		bus.pop();
//              delete p;
	}

	return 0;
}

On retrouve l'initialisation du générateur de nombres aléatoires, la déclaration du bus, qui est une pile de personnes (une pile de pointeurs sur des objets de type personne pour être exact). Viennent ensuite les "montées" des personnes, des travailleurs puis de la directrice et la descente de tout ce petit monde.

La ligne delete p; a été commentée pour améliorer la lisibilité de la sortie du programme. Oublier un delete n'est pas une faute de programmation car le ménage est fait à la fin du programme... Mais il est tout de même recommandé de mettre ceux auquels vous pensez. C'est quoi qu'il en soit une bonne habitude à prendre de libérer ce que l'on réserve lorsqu'on ne s'en sert plus. Cela sert lors du développement de modules, de composants du kernel, d'applications temps réel ou d'applications serveurs dont la durée d'exécution est très longue et qu'on ne peut se contenter du nettoyage automatique.

Pour compiler cet exemple, le répertoire doit contenir les fichiers personne.c++, personne.h, travailleur.c++, travailleur.h, directeur.c++, directeur.h et Makefile. Le Makefile en question est le suivant, il a été commenté au cours de l'article paru dans linuxmag n°47.

CXXFLAGS=-I.
OBJS=directeur.o travailleur.o personne.o ex_5_2.o

all : ex_5_2

clean_and_all: clean all

ex_5_2: $(OBJS) 
	@echo "Liaison de" $(OBJS) "en" $@
	$(CXX) $(CXXFLAGS) -o $@ $(OBJS)
	@echo "Fini, OK"
	@echo

clean:
	@echo -n "Nettoyage en cours..."
	@-$(RM) $(OBJS) ex_5_2
	@echo
	@echo "C'est propre"
	@echo

%.o: %.c++
	@echo "Compilation de" $<
	$(CXX) $(CXXFLAGS) -c $<

L'exécutable s'obtient traditionnellement en tapant make et l'exécution par ./ex_5_2 :

$ ./ex_5_2
Création d'une femme : X1 Y1
 *  27 ans
Création d'une femme : X1 Y1
 *  27 ans
Création d'un homme : X1 Y1
 *  27 ans
Création d'un homme : X1 Y1
 *  27 ans
Création d'une femme : X1 Y1
 *  27 ans
Création d'un homme : Y2 X2
 *  26 ans
Création d'une femme : Y2 X2
 *  26 ans
Création d'un homme : Y2 X2
 *  26 ans
Création d'un homme : Y2 X2
 *  26 ans
Création d'une femme : Foobar Toto
 *  45 ans
Foobar Toto
Y2 X2
Y2 X2
Y2 X2
Y2 X2
X1 Y1
X1 Y1
X1 Y1
X1 Y1
X1 Y1

Que faut-il retenir de cette exécution ? Premièrement, on peut manipuler un travailleur ou un directeur comme une personne, puisqu'ils en sont dérivés.

Deuxièmement, on voit que pour chaque type de personne, la méthode get_identification appelée est celle de la classe personne. C'est logique puisqu'on travaille sur des pointeurs sur des personne. Il serait toutefois utile de pouvoir utiliser les méthodes get_identification des classes dérivées puisqu'elles complètent la version de la classe de base.

On obtient ce comportement en déclarant la méthode comme étant virtual. Ainsi, lors de l'exécution, la méthode appelée est celle de la classe dont est instancié l'objet pointé et non plus celle de la classe "censée" être pointée. Toutefois, si la classe dérivée ne contient pas de surcharge de cette méthode virtuelle, c'est celle de la classe parente qui est utilisée. On remonte ainsi la hiérarchie des classes jusqu'à en trouver une qui fournisse une implémentation de la méthode recherchée.

Concrètement maintenant, dans notre cas, ajoutez virtual devant les déclarations de get_identification des classes personne et travailleur et éventuellement directeur puis recompilez en tapant make clean_and_all (nécessaire car on ne gère pas les dépendances avec les fichiers .h dans le Makefile) et exécutez.

Les modifications pour personne.h

	virtual std::string get_identification() const { return (prenom + " " + nom); }

Les modifications pour travailleur.h

	virtual std::string get_identification() const { return personne::get_identification()+" : "+metier; }

La sortie de l'exécution devient alors :

$ ./ex_5_2 2
Création d'un homme : X1 Y1
 *  27 ans
Création d'un homme : X1 Y1
 *  27 ans
Création d'une femme : X1 Y1
 *  27 ans
Création d'une femme : X1 Y1
 *  27 ans
Création d'un homme : X1 Y1
 *  27 ans
Création d'une femme : Y2 X2
 *  26 ans
Création d'un homme : Y2 X2
 *  26 ans
Création d'un homme : Y2 X2
 *  26 ans
Création d'une femme : Y2 X2
 *  26 ans
Création d'une femme : Foobar Toto
 *  45 ans
Madame Foobar Toto : Directeur
Y2 X2 : Boulot quelconque
Y2 X2 : Boulot quelconque
Y2 X2 : Boulot quelconque
Y2 X2 : Boulot quelconque
X1 Y1
X1 Y1
X1 Y1
X1 Y1
X1 Y1

On constate que la méthode get_identification appelée est celle de la classe de l'objet pointé (personne, travailleur ou directeur) et non plus celle de la classe qui est théoriquement pointée (personne). C'est ce que l'on appelle le polymorphisme, qui est semblable à la surcharge mais à l'envers ;-).

Plus simplement, si on a une classe personne et une classe travailleur dérivée de personne comme ici, utiliser des membres de personne à partir d'un pointeur sur travailleur est normal puisqu'un travailleur est avant tout une personne. A l'inverse, utiliser les membres d'un travailleur à partir d'un pointeur sur personne demande un peu plus de remue ménage intellectuel et s'appelle le polymorphisme. On dit, quand ça marche, que la classe de base est polymorphique. La limitation du polymorphisme est que pour appeler une méthode de travailleur, elle a du être définie dans personne et en plus, être virtuelle. Si la méthode n'est pas virtuelle, on devra au préalable, pour appeler la bonne méthode, "caster" le pointeur, c'est à dire forcer son changement de type. Nous verrons les différents types de "cast" lors d'un article futur sur le RTTI. (Quelqu'un veut une aspirine ?).

Les plus perspicaces d'entres vous vont se dire : "Mais alors, dans le cas présent, le destructeur appelé est celui de la classe personne ?". Question à laquelle je réponds "OUI". C'est pour cette raison que les destructeurs sont le plus souvent virtuels. Dans le cas des classes servant comme classe de base, les destructeurs doivent être ( - il est fortement recommandé qu'ils soient - ) virtuels sinon, vous aurez tôt ou tard des problèmes. Il suffit pour s'en convaincre d'ajouter -Wall à la variable CXXFLAGS dans le Makefile, La ligne de définition de CXXFLAGS devient donc :

CXXFLAGS=-I. -Wall

Suite à celà, taper make clean_and_all vous affichera de nouveaux Warning (tous, théoriquement. -Wall signifiant Warnings : all). Il ressembleront (entre autres) à :

personne.h:32: warning: `class personne' has virtual functions but non-virtual destructor

Ces warnings vous préviennent que g++ a détécté une classe qui sera vraisemblablement destinée à être dérivée puisque contenant des fonctions virtuelles mais que bizarrement le destructeur, lui, n'est pas virtuel.

Classes abstraites

Pour comprendre le concept de classe abstraite, on doit connaître celui de fonction virtuelle pure. Je vais vous exposer ce que sont ces deux choses.

fonctions virtuelles pures

class identifiable {
public:
        virtual string get_identification() = 0;
}

Une fonction virtuelle pure est une fonction virtuelle qui n'est pas définie dans la classe où elle est déclarée. Pour éviter que le compilateur ne se plaigne et pour ne pas les confondre avec les fonctions virtuelles "normales", on ajoute comme suffixe à leur déclaration = 0.

classes abstraites

Une classe abstraite est une classe comprenant au moins une fonction virtuelle pure. Elles sont utilisées comme classes de bases uniquement et aident à la construction d'interfaces. Elles peuvent assurer une partie de traitement mais forcent leurs classes filles destinées à être instanciées à définir les fonctions virtuelles. Une classe fille d'une classe abstraite qui n'implémente pas toutes les fonctions virtuelles pures de sa classe parente est elle même une classe abstraite. Enfin, il faut savoir qu'une classe abstraite ne peut pas être instanciée quelque soit le nombre de fonctions virtuelles pures qu'elle contient.

Prenons un exemple illustrant tout ce qui vient d'être dit :

/* ex_5_3.c++ */
#include <iostream>
#include <string>
using namespace std;

/* *** identifiable *** */
class identifiable {
	string article;
protected:
	void set_genre(const int&);
	string get_article() const { return article; };
public:
        virtual string get_identification() const = 0;
};

void identifiable::set_genre(const int& genre) {
	switch (genre) {
  		case 1:
			article = "Un ";
			break;
  		case 2:
			article = "Une ";
			break;
		default:
			article = "";
	}
}

/* *** utile *** */
class utile {
public:
        virtual string get_fonction() const = 0;
};

/* *** objet *** */
class objet : public identifiable, public utile {
	string nom, fonction;

public:
	objet(string n, string f, int genre);
	string get_identification () const { return (get_article() + nom); };
	string get_fonction () const { return fonction; };
};

objet::objet(string n, string f, int genre=0) :
	nom(n),
	fonction(f) {
	set_genre(genre);
}

/* *** main *** */
int main (void) {
//	identifiable i; // provoque une erreur à la compilation
	objet o("balle", "rebondit", 2);
	string verite = o.get_identification();
	verite += " ";
	verite += o.get_fonction();
	verite += ".";
	cout << verite << endl;
	return 0;
}

On compile et exécute :

$ g++ -o ex_5_3 ex_5_3.c++ 
$ ./ex_5_3
Une balle rebondit.

J'en entends déjà dire "ça on le savait déjà ...". Certes, on n'apprend rien sur la vie avec ce programme, par contre on peut facilement en examiner la structure. Il comporte deux classes abstraites, identifiable et utile, qui vont nous servir à définir des objets identifiables et utiles, comme vous auriez pu vous en douter.

La classe identifiable contient une propriété privée, l'article ("Un ", "Une " ou rien) qui servira à construire son identification. Cette propriété étant privée, la classe fournit deux méthodes permettant d'en modifier et récupérer le contenu, set_sexe assigne sa valeur à l'article, "Un " pour un sexe masculin, "Une " pour un sexe féminin et rien pour les autres. Ces méthodes sont protected afin de garantir leur utilisation uniquement par les classes dérivées (cf. article de février). Enfin, pour permettre la récupération de l'identification des objets identifiables, on impose la définition par les classes dérivées non abstraites de la méthode publique get_identification. La déclaration de cette fontion virtuelle pure rend impossible la création d'objets de la classe identifiable. Votre compilateur préféré devrait donc vous reporter une erreur si vous tentez de compiler le programme ci-dessus en ayant décommenté la ligne suivante :

        identifiable i;

Par exemple, chez moi, g++ (version 2.95.3) m'informe que je ne peux créer un objet i de type identifiable car la classe contient des fonctions virtuelles pures. Vous devriez tous obtenir une réponse de ce type, même si vous utilisez un autre compilateur et même avec un autre système d'exploitation. Sinon, pressez vous d'en changer.

$ g++ -o ex_5_3 ex_5_3.c++
ex_5_3.c++: In function `int main()':
ex_5_3.c++:54: cannot declare variable `i' to be of type `identifiable'
ex_5_3.c++:54:   since the following virtual functions are abstract:
ex_5_3.c++:13:  class string identifiable::get_identification() const

La classe utile est extrêmement simple puisqu'elle n'est constituée que d'une fonction virtuelle pure publique, get_fonction. Les classes héritant de utile devront donc définir une méthode get_fonction pour être instanciables.

La classe objet hérite des deux classes précédentes. Pour pouvoir instancier des objets identifiables et utiles, la classe objet fournit l'implémentation des deux fonctions virtuelles pures get_identification de la classe identifiable et get_fonction de la classe utile. On retrouve la méthode identifiable::get_article utilisée pour construire l'identification de l'objet et set_genre utilisée dans le constructeur de la classe objet.

Bien, ça marche mais ça semble bien complexe pour finalement pas grand chose. On est passés par trois classes et des fonctions virtuelles pures pour faire un objet ... L'intérêt n'est pas évident.
Admettons maintenant que nous ayons besoin de travailler avec des trucs et des machins en plus de nos objets. Si on souhaite afficher l'identification de chacun, le fait qu'ils héritent tous d'identifiable rend la procédure immédiate. Ajoutons les classes truc et machin à notre programme et modifions le main pour démontrer ce qui vient d'être dit :

/* ex_5_4.c++ */
[...]
#include <queue>
[...]

/* *** truc *** */
class truc : public identifiable {
public:
	string get_identification () const { return "un truc."; };
};

/* *** machin *** */
class machin : public identifiable {
public:
	string get_identification () const { return "un machin."; };
};

/* *** main *** */
int main (void) {
	queue<identifiable*> la_queue;
	int nb;

	cout << "Combien de choses ?" << endl << "> ";
	cin >> nb;
	while(nb--) la_queue.push(new objet("chose", "utile", 2));
	cout << "Combien de trucs ?" << endl << "> ";
	cin >> nb;
	while(nb--) la_queue.push(new truc());
	cout << "Combien de machins ?" << endl << "> ";
	cin >> nb;
	while(nb--) la_queue.push(new machin());

	while (!la_queue.empty()) {
		identifiable* ptr = la_queue.front();
		cout << ptr->get_identification() << endl;
		delete ptr;
		la_queue.pop();
	}
	return 0;
}

Les classes truc et machin sont extrêmement simples, elles dérivent d'identifiable mais n'en utilisent même pas les membres set_genre ou get_article. Elles se contentent de fournir l'implémentation nécessaire de la fonction viruelle pure get_identification.
On peut voir celà comme le respect d'une norme. Si on veut se réclamer compatible avec une norme, on doit fournir une implémentation qui en respecte les règles. La construction d'objets par assemblage d'interfaces (ou classes abstraites) est très semblable à ce principe dans le sens où la création des classes objet, truc et machin est un peu plus complexe mais assure qu'elles sont "conformes" à la "norme" identifiable. On peut ainsi en manipuler les instances comme des objets identifiables et profiter de toutes les facilités de la norme (ici, le traitement lié à l'article par exemple).

Le main confirme cela en démontrant comment ajouter à une queue de pointeur sur des objets de type identifiable un nombre quelconque d'objets "respectant" (héritant et implémentant) l'interface identifiable. Il est constitué de la déclaration de la queue (n'oubliez pas le #include correspondant en début de programme.
On demande ensuite à l'utilisateur de spécifier le nombre de chaque type d'objet à ajouter à la queue. Les objets sont alors créés et ajoutés.
L'exécution se termine par l'affichage et la suppression des éléments de la queue. Attention : on n'oublie pas le delete pour libérer la mémoire ! La méthode pop() detruit le pointeur, pas l'objet pointé.

En C, on pourrait faire ça avec des pointeurs void*, des casts, des structures contenant des pointeurs sur fonctions et une implémentation de queue (ou en utilisant la glib). Qui a dit que le C++ est un langage compliqué ? ;-)

Une compilation / exécution :

$ g++ -o ex_5_4 ex_5_4.c++ 
$ ./ex_5_4
Combien de choses ?
> 3
Combien de trucs ?
> 2
Combien de machins ?
> 4
Une chose
Une chose
Une chose
un truc.
un truc.
un machin.
un machin.
un machin.
un machin.

Héritage non public

Je vais finir cet article par un paragraphe sur les modes d'héritage non publics, c'est à dire les modes protected et private. Ce sont les mêmes mots clés que l'ont utilise pour définir les "droits d'accès" aux membres de classes et nous allons voir que ce n'est pas un hasard.

L'héritage le plus utilisé (celui que nous avons utilisé jusque là) est l'héritage public. C'est à dire que les membres publics de la classe parente deviennent des membres publics de la classe fille, de même que les membres protégés restent protégés. Ce n'est toute fois pas le cas par défaut. L'héritage par défaut est privé, c'est à dire que tous les membres publics et protégés de la classe de base sont accessible à la classe fille mais deviennent privés dans cette dernière et ne sont donc plus héritables. L'héritage protégé rend les membres publics et protégés de la classe de base protégés dans la classe fille. C'est à dire que les membres publics ne sont plus accessibles en passant par les objets instanciés de la classe fille. Bien sûr, quel que soit le mode d'héritage utilisé les membres privés de la classe de base ne sont jamais hérités.

Pour résumer, voici un tableau expliquant comment évoluent les membres de la classe de base dans la classe dérivée selon le mode d'héritage utilisé :

Classe de baseClasse dérivée
héritage publicpublicpublic
protectedprotected
héritage protectedpublicprotected
protectedprotected
héritage privatepublicprivate
protectedprivate

Et pour bien enfoncer le clou, prenons un exemple.

01: /* ex_5_5.c++ */
02: #include <iostream>
03: using namespace std;
04: 
05: class A {
06: 	int i_priv;
07: protected:
08: 	int i_prot;
09: public:
10: 	int i_publ;
11: 	A();
12: };
13: 
14: A::A() : i_priv(1), i_prot(2), i_publ(3) {
15: 	cout << "--------" << endl;
16: 	cout << "A :" << endl;
17: 	cout << i_publ << endl;
18: 	cout << i_prot << endl;
19: 	cout << i_priv << endl;
20: }
21: 
22: 
23: class B_priv : A {
24: public:
25: 	B_priv();
26: };
27: 
28: B_priv::B_priv() {
29: 	cout << "B_priv :" << endl;
30: 	cout << i_publ << endl;
31: 	cout << i_prot << endl;
32: //	cout << i_priv << endl;
33: }
34: 
35: class B_prot : protected A {
36: public:
37: 	B_prot();
38: };
39: 
40: B_prot::B_prot() {
41: 	cout << "B_prot :" << endl;
42: 	cout << i_publ << endl;
43: 	cout << i_prot << endl;
44: //	cout << i_priv << endl;
45: }
46: 
47: class B_publ : public A {
48: public:
49: 	B_publ();
50: };
51: 
52: B_publ::B_publ() {
53: 	cout << "B_publ :" << endl;
54: 	cout << i_publ << endl;
55: 	cout << i_prot << endl;
56: //	cout << i_priv << endl;
57: }
58: 
59: class C_prot : public B_prot {
60: public:
61: 	C_prot();
62: };
63: 
64: C_prot::C_prot() {
65: 	cout << "C_prot :" << endl;
66: 	cout << i_publ << endl;
67: 	cout << i_prot << endl;
68: //	cout << i_priv << endl;
69: }
70: 
71: class C_priv : public B_priv {
72: public:
73: 	C_priv();
74: };
75: 
76: C_priv::C_priv() {
77: 	cout << "C_priv :" << endl;
78: //	cout << i_publ << endl;
79: //	cout << i_prot << endl;
80: //	cout << i_priv << endl;
81: }
82: 
83: int main() {
84: 	A a;
85: 	B_publ b_publ;
86: 	B_prot b_prot;
87: 	B_priv b_priv;
88: 	C_prot c_prot;
89: 	C_priv c_priv;
90: 
91:	cout << "--------" << endl;
92: 	cout << "main :" << endl;
93: 	cout << a.i_publ << endl;
94: 	cout << b_publ.i_publ << endl;
95: //	cout << b_prot.i_publ << endl;
96: //	cout << b_priv.i_publ << endl;
97: 	return 0;
98: }

Dans ce code, les lignes commentées sont celles qui provoqueraient des erreurs à la compilation.

Nous avons là une classe A avec 3 membres, i_priv, private, i_prot, protected et i_publ, public. Ces trois propriétés sont accessibles dans A (lignes 17-19) et la seule la propriété publique est accessible dans le main, par le biais d'une instance de A.

Viennent ensuite B_priv, B_prot et B_publ, qui toutes trois sont des classes filles de A mais utilisant respectivement les modes d'héritage privé, protégé et public. On voit que les propriétés publiques et protégées sont hérités par les trois modes d'héritages puisqu'elles sont utilisées dans les trois constructeurs (lignes 30-32, 42-44, 54-56). Bien entendu, on voit également que nulle part les propriétés privées de la classe de base ne sont accessibles aux classes filles. Ce qui est plus inhabituel est que la propriété publique n'est accessible qu'au travers de l'instance de la classe B_publ puisque, comme dit dans le tableau précédent, la propriété i_publ, publique dans A et B_publ est devenue protégée dans B_prot et privée dans B_priv.

Enfin, on a la preuve que i_publ et i_prot sont bien protégées et non privées dans B_prot mais privées dans B_priv en créant C_prot et C_priv, classes héritant selon le mode public de B_prot et B_priv (lignes 59-81). Une classe C_publ héritant publiquement de B_publ n'a pas été créée car elle aurait été identique à B_publ au niveau des droits d'accès.

La sortie du programme :

$ ./ex_5_5
--------
A :
3
2
1
--------
A :
3
2
1
B_publ :
3
2
--------
A :
3
2
1
B_prot :
3
2
--------
A :
3
2
1
B_priv :
3
2
--------
A :
3
2
1
B_prot :
3
2
C_prot :
3
2
--------
A :
3
2
1
B_priv :
3
2
C_priv :
--------
main :
3
3

On arrête temporairement "la prise de tête" pour passer le mois prochain à l'étude pratique des flux et les possibilités fournies par la librairie standard pour les manipuler. Au menu, il y aura au moins les entrées/sorties sur fichier et les flux de/sur chaînes (enfin, stringstream, traduisez comme il vous plaît)...
Je vous rapelle que les anciens articles sont diponibles dans les vieux numéros de linuxmag pour une version sur papier glacé ou online sur mon site où se trouvent également les codes sources des exemples.
@+

Xavier Garreau - <xavier@xgarreau.org>
http://www.xgarreau.org/

Ingénieur de recherche PRIM'TIME TECHNOLOGY
http://www.prim-time.fr/

Membre fondateur du ROCHELUG
http://www.rochelug.org/

Références :


Précédent Index Suivant

a+

Auteur : Xavier GARREAU
Modifié le 10.09.2004

Rechercher :

Google
 
Web www.xgarreau.org