OVH Cloud OVH Cloud

Forme canonique de Coplien et poymorphisme

137 réponses
Avatar
Y a n n R e n a r d
Bonjour à tous,

tout d'abord, merci à tous pour ce newsgroup très instructif, c'est la
première fois que je poste, alors je vais essayer de ne pas me faire
incendier par Fabien ;) :p

Voila mon problème : j'ai un client qui souhaite que toutes les classes
définies dans le projet sur lequel je travaille soient sous la forme
canonique de Coplien (pour ceux qui ne connaissent pas : constructeur
par défaut, constructeur par copie, operateur d'affectation et
destructeur virtuel ou non). Pour que ce soit plus clean (selon eux), le
client demande que toutes les classes dérivent d'une classe de base...
Sauf que je ne vois pas du tout l'intéret de faire ca car je ne vois pas
en quoi ca force à respecter la forme canonique de Coplien.

Concernant l'operateur d'affectation, si je le met virtuel (est ce
quelque chose de propre ?!) comment puis je faire en sorte que les
classes filles utilisent un opérateur correct ? cast dynamique ?

Concernant l'operateur de recopie, là, je vois pas du tout du tout du
tout :)

Bref, je comprends pas du tout cette demande, si quelqu'un pense que
c'est raisonable ou explicable, je veux bien une précision sur l'utilité...

Pour terminer, il faut aussi déporter le code du constructeur et du
destructeur dans une fonction séparée, et là, je comprends plus du
tout... genre quelque chose comme ca :

T::T(void)
{
construct();
}

T::~T(void)
{
destruct();
}

void T::construct(void)
{
// ...
}

void T::destruct(void)
{
// ...
}

Voila voila, votre avis est le bienvenu :)
Merci d'avance,

Yann Renard

10 réponses

Avatar
kanze
Jean-Marc Bourguet wrote in message
news:...
drkm writes:

Au fait, y a-t-il des cas où une classe possédant des membres
virtuels peut légitimement ne pas déclarer son destructeur virtuel ?


Oui. Si elle n'est jamais détruite que dans des situations où on
connait son type. Le destructeur est généralement privé dans ces cas
là.


Un déstructeur privé dans ce cas-là ? À peu près le seul cas que je
connais où on connaît systèmatiquement le type de l'objet, bien qu'il y
a des fonctions virtuelles, c'est quand il s'agit des temporaires, par
exemple, les noeuds des expressions (par exemple dans un calcul
matriciel -- aujourd'hui, on privilèrera les templates, mais avant qu'il
n'y avait les templates, on se servait volentiers de la polymorphisme à
base des fonctions virtuelles, en sachant que le compilateur savait
toujours le type réel, et donc pouvait résoudre les fonctions
statiquement, et même les générer en ligne).

En fait, si une classe a un destructeur privé, on ne peut pas en
dériver. Et il y a beaucoup de règles de codage qui interdit d'augmenter
la protection d'une fonction, c-à-d de rendre privé dans une classe
dérivée ce qui ne l'était pas dans une classe de base. J'aurais donc une
forte tendance à éviter les destructeurs privés dans des classes avec
fonctions virtuelles.

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Jean-Marc Bourguet
writes:

Jean-Marc Bourguet wrote in message
news:...
drkm writes:

Au fait, y a-t-il des cas où une classe possédant des membres
virtuels peut légitimement ne pas déclarer son destructeur virtuel ?


Oui. Si elle n'est jamais détruite que dans des situations où on
connait son type. Le destructeur est généralement privé dans ces cas
là.


Un déstructeur privé dans ce cas-là ? À peu près le seul cas que je
connais où on connaît systèmatiquement le type de l'objet, bien qu'il y
a des fonctions virtuelles, c'est quand il s'agit des temporaires, par
exemple, les noeuds des expressions (par exemple dans un calcul
matriciel -- aujourd'hui, on privilèrera les templates, mais avant qu'il
n'y avait les templates, on se servait volentiers de la polymorphisme à
base des fonctions virtuelles, en sachant que le compilateur savait
toujours le type réel, et donc pouvait résoudre les fonctions
statiquement, et même les générer en ligne).


Les cas que j'ai rencontre ou on connaissait systematiquement le type
lors de la destruction, c'est quand la duree de vie etait geree par
l'objet les ayant construit. Il etait "friend" pour pouvoir les
detruire.

En fait, si une classe a un destructeur privé, on ne peut pas en
dériver.


Oops, je pensais protege.

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org



Avatar
Laurent Deniau
Jean-Marc Bourguet wrote:
Laurent Deniau writes:


Jean-Marc Bourguet wrote:

Je ne comprends pas plus que toi. Faut demander au client.
Attention que pendant l'execution du constructeur et du destructeur,
le type dynamique n'est pas le plus derive. Autrement dit "les
virtuelles ne marchent pas".


Je ne comprends pas ce que tu veux dire. D'apres ce que je sais (C++98)





- Les constructeurs ne peuvent pas etre virtuels, donc forcement
dans B::B(), this est de type B*. Et on ne peut pas appeler de
methode virtuelle dans le constructeur.



Si on peut appeler des membres virtuels. Mais leur resolution n'est
pas celle a laquelle certains (en particulier ceux qui ont fait du
Java) s'attende.


Pour faire des "factory" on fait l'inverse, une methode virtuelle
appelle le constructeur.

- un destructeur virtuel n'est pas different d'une autre methode
virtuelle sur ce point. Dans virtual B::~B(), this peut etre de type
D* ou D derive de B et on peut le retrouver avec un
dynamic_cast<>().



Non.


Peux-tu m'eclairer sur tes propos?



Pendant l'execution du constructeur et du destructeur, l'objet est du
type de la classe construite (et non de la classe la plus derivee).


Pour revenir sur ce point.

soit B derive de A et redefinit B::clear(). Dans B::~B() on appelle
A::~A(). Donc l'appel de B::~B() donne:

virtual A::~A() // this est de type A* (en fait un B* lors de l'appel)
appelle virtual A::close() // this est de type A* ou B*?
appelle virtual A::clear() // appelle A::clear() ou B::clear() ?
virtual void A::operator delete(void*, size_t) // virtual sans effet.

Mais question soujacente est: que fait le compilateur avec ce scenario?

1) il change la reference de la vtbl (et donc aussi des RTTI) dans
l'objet pour passer de B::vtbl a A::vtbl avant d'entrer dans A::~A().

2) il change sa semantique de resolution d'appel de methode dans le
destructeur en prenant toujours les methodes de A:: (+ classes de base
si necessaire)?

S'il fait 1), le scenario ci-dessus est ok. Mais alors comment
determine-t-il correctement le size_t dans A::operator delete(void*,
size_t) puisqu'il n'a plus acces au RTTI de B?

S'il fait 2), on a un UB dans A::clear() si this est un B*. Dans ce cas,
le C++ nous protege dans le destructeur contre ce type de UB mais pas
au-dela du dtor. La semantique change donc B* -> A::~A() devient A* ->
A::close() redevient un B*. C'est pas plus dangeureux cette fausse
impression de securite?

alors ca marche comment tout ca? ;-)

Appeler un membre virtuel sur this a un comportement defini: la
fonction sera celle de la classe construite ou de l'ancetre la
fournissant (donc c'est un probleme si elle est pure).


Je ne crois pas que tu puisses compiler du code qui aboutisse a un appel
de methode pure.

a+, ld.



Avatar
Jean-Marc Bourguet
Laurent Deniau writes:

Jean-Marc Bourguet wrote:
[...]

Pendant l'execution du constructeur et du destructeur, l'objet est du
type de la classe construite (et non de la classe la plus derivee).


Pour revenir sur ce point.

soit B derive de A et redefinit B::clear(). Dans B::~B() on appelle
A::~A(). Donc l'appel de B::~B() donne:

virtual A::~A() // this est de type A* (en fait un B* lors de l'appel)
appelle virtual A::close() // this est de type A* ou B*?


A*

appelle virtual A::clear() // appelle A::clear() ou B::clear() ?


A::clear

C'est clair dans 12.7/3 (je l'ai cite dans la discussion et j'ai pas
envie de le retaper, mon PDF est protege contre la copie).

virtual void A::operator delete(void*, size_t) // virtual sans effet.

Mais question soujacente est: que fait le compilateur avec ce scenario?

1) il change la reference de la vtbl (et donc aussi des RTTI) dans l'objet
pour passer de B::vtbl a A::vtbl avant d'entrer dans A::~A().


Ou l'equivalent.

[...]
Appeler un membre virtuel sur this a un comportement defini: la
fonction sera celle de la classe construite ou de l'ancetre la
fournissant (donc c'est un probleme si elle est pure).


Je ne crois pas que tu puisses compiler du code qui aboutisse a un
appel de methode pure.


struct A {
A() { fn1(); fn2(); }
void fn1() { fn2(); }
virtual void fn2() = 0;
};

Il y a des chances que les compilateurs avertissent pour l'appel
direct a fn2, mais celui indirect par fn1 a plus de chance de passer
inappercu, en particulier si le code de fn2() n'est pas visible quand
on compile le constructeur (tu les mets non inline dans des unites de
compilation differente). Si ca ne te conviens pas, je te fais une
version avec des pointeurs de fonction.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Laurent Deniau
Jean-Marc Bourguet wrote:
Laurent Deniau writes:


Jean-Marc Bourguet wrote:


[...]

Pendant l'execution du constructeur et du destructeur, l'objet est du
type de la classe construite (et non de la classe la plus derivee).


Pour revenir sur ce point.

soit B derive de A et redefinit B::clear(). Dans B::~B() on appelle
A::~A(). Donc l'appel de B::~B() donne:

virtual A::~A() // this est de type A* (en fait un B* lors de l'appel)
appelle virtual A::close() // this est de type A* ou B*?



A*


appelle virtual A::clear() // appelle A::clear() ou B::clear() ?



A::clear


Donc B* devient un A* et reste un A*. Donc comment delete retrouve la
taille de l'objet a detruire et surtout en quoi declarer le destructeur
virtual resoud le probleme s'il a le comportement que tu dis?

C'est clair dans 12.7/3 (je l'ai cite dans la discussion et j'ai pas
envie de le retaper, mon PDF est protege contre la copie).


Ok. Mais j'essaye de comprendre comment il fait concretement. Pour
l'instant j'ai deux voies orthogonales qui menent toutes les deux a un
probleme, chacun different et critique. Donc je cherche a savoir s'il y
a une troisieme voie. J'ai du rate une marche qque part mais laquelle.

virtual void A::operator delete(void*, size_t) // virtual sans effet.

Mais question soujacente est: que fait le compilateur avec ce scenario?

1) il change la reference de la vtbl (et donc aussi des RTTI) dans l'objet
pour passer de B::vtbl a A::vtbl avant d'entrer dans A::~A().



Ou l'equivalent.


Qu'est ce que tu entends par l'equivalent? Est-ce que tu veux dire qu'il
fait quelque chose d'hybride? quoi?

J'ai du mal a croire qu'il touche au pointeur de la vtbl. hormis le
probleme du delete, immagine que que A::~A() appelle une methode
virtuelle qui leve *legalement* une exception (cas ou on est pas dans un
stack unwinding). Comment le mecanisme des exceptions fait pour
retrouver le type/taille de l'objet et le copier correctement dans son
buffer? Il croit avoir un this en A* alors qu'il est en B*. UB, fuite de
ressources?

Appeler un membre virtuel sur this a un comportement defini: la
fonction sera celle de la classe construite ou de l'ancetre la
fournissant (donc c'est un probleme si elle est pure).


Je ne crois pas que tu puisses compiler du code qui aboutisse a un
appel de methode pure.



struct A {
A() { fn1(); fn2(); }
void fn1() { fn2(); }
virtual void fn2() = 0;
};

Il y a des chances que les compilateurs avertissent pour l'appel
direct a fn2, mais celui indirect par fn1 a plus de chance de passer
inappercu, en particulier si le code de fn2() n'est pas visible quand
on compile le constructeur (tu les mets non inline dans des unites de
compilation differente). Si ca ne te conviens pas, je te fais une
version avec des pointeurs de fonction.


le code n'a pas besoin d'etre visible (idem pour les pointeurs). Tant
que le compilateur ne trouve pas de redeclaration dans les classes
derivee, ces classes restent abstraite et l'acces a fn2 est illegal. Il
a besoin de la definition des classes, pas de leur implementation.

Donc depuis A::A() c'est assez clair, on appelle A::fn1() qui appelle
A::fn2() qui est virtuelle pure. Il n'y a pas d'autre chemin possible
donc erreur.

Si tu pars d'un B::fn1() alors la aussi c'est clair, si B::fn2() n'est
pas *redeclaree* non virtuelle pure, re-erreur.

Pas si facile de mettre le compilateur en defaut. C'est un peu comme les
reference nul. J'ai pas encore trouve de cas ou le probleme persiste.

a+, ld.



Avatar
Gabriel Dos Reis
Laurent Deniau writes:

| Jean-Marc Bourguet wrote:
| > Je ne comprends pas plus que toi. Faut demander au client.
| > Attention que pendant l'execution du constructeur et du destructeur,
| > le type dynamique n'est pas le plus derive. Autrement dit "les
| > virtuelles ne marchent pas".
|
| Je ne comprends pas ce que tu veux dire. D'apres ce que je sais (C++98)
|
| - Les constructeurs ne peuvent pas etre virtuels, donc forcement dans
| B::B(), this est de type B*. Et on ne peut pas appeler de methode
| virtuelle dans le constructeur.

Je soupçonne une erreur de traduction. En C++, il a toujours été
possible d'appeler une fonction virtuelle dans un constructeur ou
destructeur ; seulement le mécanisme de résolution n'est pas dynamique
mais statique.

-- Gaby
Avatar
Jean-Marc Bourguet
Laurent Deniau writes:

Jean-Marc Bourguet wrote:
Laurent Deniau writes:

Jean-Marc Bourguet wrote:
[...]


Pendant l'execution du constructeur et du destructeur, l'objet est du
type de la classe construite (et non de la classe la plus derivee).


Pour revenir sur ce point.

soit B derive de A et redefinit B::clear(). Dans B::~B() on appelle
A::~A(). Donc l'appel de B::~B() donne:

virtual A::~A() // this est de type A* (en fait un B* lors de l'appel)
appelle virtual A::close() // this est de type A* ou B*?
A*


appelle virtual A::clear() // appelle A::clear() ou B::clear() ?
A::clear



Donc B* devient un A* et reste un A*.

Donc comment delete retrouve la taille de l'objet a
detruire et surtout en quoi declarer le destructeur
virtual resoud le probleme s'il a le comportement que tu
dis?


C'est le problème de l'implémentation. Je ne crois pas que
le destructeur virtuel ait grand chose à voir avec la taille
à libérere (la technique classique c'est quand même que
l'allocateur connait la taille de ses blocs) mais plus avec
l'exécution du destructeur de la classe la plus dérivée et
au passage à l'operateur delete du bon pointeur en cas
d'héritage multiple.

Mais bon, c'est vrai qu'on peut utiliser des opérateurs
delete qui prennent la taille aussi.

delete a;

peut donc être compilé en qqch du genre (j'ai jamais
travaillé sur un compilateur C++, il y a peut-être des
choses que j'oublie):

__base = a->__get_most_derived_object();
__sz = a->__get_size_of_most_derived_object();
__delete = a->__get_delete_operator();
a->__destructor_in_charge_of_virtual_members(__base);
(*__delete)(__base, __sz);


virtual void A::operator delete(void*, size_t) // virtual sans effet.

Mais question soujacente est: que fait le compilateur avec ce scenario?

1) il change la reference de la vtbl (et donc aussi des RTTI) dans l'objet
pour passer de B::vtbl a A::vtbl avant d'entrer dans A::~A().
Ou l'equivalent.



Qu'est ce que tu entends par l'equivalent?


Le vtbl n'est qu'un moyen d'implémenter le méchanisme en
C++, on peut vraissemblablement en imaginer d'autres.

Est-ce que tu veux dire qu'il fait quelque chose
d'hybride? quoi?

J'ai du mal a croire qu'il touche au pointeur de la
vtbl. hormis le probleme du delete, immagine que que
A::~A() appelle une methode virtuelle qui leve
*legalement* une exception (cas ou on est pas dans un
stack unwinding). Comment le mecanisme des exceptions fait
pour retrouver le type/taille de l'objet et le copier
correctement dans son buffer?


Tu veux dire qu'on jette *this dans un destructeur et que tu
veux qu'il copie l'objet le plus dérivé qui est déjà détruit
en partie?

Il croit avoir un this en A* alors qu'il est en B*. UB,
fuite de ressources?

Appeler un membre virtuel sur this a un comportement defini: la
fonction sera celle de la classe construite ou de l'ancetre la
fournissant (donc c'est un probleme si elle est pure).



Je ne crois pas que tu puisses compiler du code qui aboutisse a un
appel de methode pure.
struct A {

A() { fn1(); fn2(); }
void fn1() { fn2(); }
virtual void fn2() = 0;
};
Il y a des chances que les compilateurs avertissent pour l'appel
direct a fn2, mais celui indirect par fn1 a plus de chance de passer
inappercu, en particulier si le code de fn2() n'est pas visible quand
on compile le constructeur (tu les mets non inline dans des unites de
compilation differente). Si ca ne te conviens pas, je te fais une
version avec des pointeurs de fonction.


le code n'a pas besoin d'etre visible (idem pour les
pointeurs). Tant que le compilateur ne trouve pas de
redeclaration dans les classes derivee, ces classes
restent abstraite et l'acces a fn2 est illegal. Il a
besoin de la definition des classes, pas de leur
implementation.

Donc depuis A::A() c'est assez clair, on appelle A::fn1()
qui appelle A::fn2() qui est virtuelle pure. Il n'y a pas
d'autre chemin possible donc erreur.


* Le code que j'ai donné compile normalement sans erreur.
Il répond donc à ta demande (un code qu'on puisse compiler
qui aboutisse à un appel de méthode pure). Il est même à
mon avis conforme si en plus on donne une implémentation
de A::fn2(), ce qui est autorisé.

* Je ne vois pas quand un compilateur pourrait donner une erreur
pour l'appel via fn1 si les compilations de A::fn1 et de A::~A
sont faites en parallèle. On pourrait imaginer une telle
vérification statique au link, mais c'est d'une complexité
supérieure à ce qu'attends le C++ de l'éditeur de liens.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org




Avatar
kanze
Gabriel Dos Reis wrote in message
news:...
Laurent Deniau writes:

| Jean-Marc Bourguet wrote:
| > Je ne comprends pas plus que toi. Faut demander au client.
| > Attention que pendant l'execution du constructeur et du
| > destructeur, le type dynamique n'est pas le plus derive. Autrement
| > dit "les virtuelles ne marchent pas".

| Je ne comprends pas ce que tu veux dire. D'apres ce que je sais (C++98)

| - Les constructeurs ne peuvent pas etre virtuels, donc forcement
| dans B::B(), this est de type B*. Et on ne peut pas appeler de
| methode virtuelle dans le constructeur.

Je soupçonne une erreur de traduction.


N'est-ce pas. Ce qui est écrit est un contre-sens. D'abord, parce que
dans toute fonction membre de B (non seulement le constructeur), this a
le type B*. Et deuxièmement, parce qu'il n'y a absolument pas de
problème à appeler les fonctions virtuelles dans le constructeur.

En C++, il a toujours été possible d'appeler une fonction virtuelle
dans un constructeur ou destructeur ; seulement le mécanisme de
résolution n'est pas dynamique mais statique.


Non plus. L'appel d'une fonction virtuelle dans un constructeur ou dans
un destructeur marche exactement comme l'appel d'une fonction virtuelle
n'importe où ailleurs : il se résoud selon le type dynamique de l'objet
(et non le type statique du pointeur). La seule chose à se rappeler,
c'est que pendant l'execution d'un constructeur ou d'un destructeur de
la class C, le type dynamique est C. On ne tient pas compte du futur
(dans le cas d'un constructeur) ni du passé (dans le cas d'un
destructeur).

J'avoue ne pas comprendre tous ces histoires. Pour une fois qu'en C++,
on a quelque chose de simple, clair et cohérent, les gens s'en
plaignent, et cherche une complexité qu'il n'y est pas. Le type
dynamique d'un objet change lors de sa construction et de sa
destruction. C'est tout ce qu'il faut savoir pour tout comprendre.

(Enfin, c'est comme ça que je le comprends, et c'est comme ça qu'il me
semble le plus clair.)

--
James Kanze GABI Software http://www.gabi-soft.fr
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
K. Ahausse
a écrit dans le message de
news:
Gabriel Dos Reis wrote in message
news:...
Laurent Deniau writes:

| Jean-Marc Bourguet wrote:
| > Je ne comprends pas plus que toi. Faut demander au client.
| > Attention que pendant l'execution du constructeur et du
| > destructeur, le type dynamique n'est pas le plus derive. Autrement
| > dit "les virtuelles ne marchent pas".

| Je ne comprends pas ce que tu veux dire. D'apres ce que je sais
(C++98)



| - Les constructeurs ne peuvent pas etre virtuels, donc forcement
| dans B::B(), this est de type B*. Et on ne peut pas appeler de
| methode virtuelle dans le constructeur.

Je soupçonne une erreur de traduction.


N'est-ce pas. Ce qui est écrit est un contre-sens. D'abord, parce que
dans toute fonction membre de B (non seulement le constructeur), this a
le type B*. Et deuxièmement, parce qu'il n'y a absolument pas de
problème à appeler les fonctions virtuelles dans le constructeur.

En C++, il a toujours été possible d'appeler une fonction virtuelle
dans un constructeur ou destructeur ; seulement le mécanisme de
résolution n'est pas dynamique mais statique.


Non plus. L'appel d'une fonction virtuelle dans un constructeur ou dans
un destructeur marche exactement comme l'appel d'une fonction virtuelle
n'importe où ailleurs : il se résoud selon le type dynamique de l'objet
(et non le type statique du pointeur). La seule chose à se rappeler,
c'est que pendant l'execution d'un constructeur ou d'un destructeur de
la class C, le type dynamique est C. On ne tient pas compte du futur
(dans le cas d'un constructeur) ni du passé (dans le cas d'un
destructeur).

J'avoue ne pas comprendre tous ces histoires. Pour une fois qu'en C++,
on a quelque chose de simple, clair et cohérent, les gens s'en
plaignent, et cherche une complexité qu'il n'y est pas. Le type
dynamique d'un objet change lors de sa construction et de sa
destruction. C'est tout ce qu'il faut savoir pour tout comprendre.


Comme le faisait remarquer Jean-Marc Bourguet, le problème provient,
peut-être, de la confusion entre fonction virtuelle et fonction virtuelle
pure.


Avatar
Jean-Marc Bourguet
"K. Ahausse" writes:

Comme le faisait remarquer Jean-Marc Bourguet, le problème provient,
peut-être, de la confusion entre fonction virtuelle et fonction virtuelle
pure.


Je ne vois pas ce que les virtuelles pures viennent faire ici.

Ce sont des membres virtuels comme les autres a deux differences pres:
- les descendants instanciables sont obliges de les supplanter
- on peut ne pas les definir (a l'exception du destructeur)

Tu confonds peut-etre avec l'autre partie de la discussion avec
Laurent ou j'indique qu'on peut arriver a cause de ces regles sur le
type dynamique durant l'execution des constructeurs et des
destructeurs a appeler un membre virtuel pur sans le faire de maniere
qualifiee (on peut naturellement le faire de maniere qualifiee dans
d'autres contextes).

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org