Les variables privées sont un moyen de masquer la complexité et les détails d'implémentation à l'utilisateur d'une classe. C'est une fonctionnalité plutôt sympa. Mais je ne comprends pas pourquoi en c ++ nous devons les mettre dans l'en-tête d'une classe. Je vois deux inconvénients ennuyeux à cela:
Y a-t-il une raison conceptuelle derrière cette exigence? Est-ce seulement pour faciliter le travail du compilateur?
C'est parce que le compilateur C++ doit connaître la taille réelle de la classe afin d'allouer la bonne quantité de mémoire à l'instanciation. Et la taille comprend tous les membres, même les membres privés.
Une façon d'éviter cela est d'utiliser l'idiome Pimpl , expliqué par Herb Sutter dans sa série Guru of the Week # 24 et # 28 .
En effet, ceci (ou plus généralement, la distinction en-tête/fichier source et #include
s) est un obstacle majeur en C++, hérité de C. Retour à l'époque C++ C a été créé, il n'y avait pas encore d'expérience dans le développement de logiciels à grande échelle, où cela commence à poser de réels problèmes. Les leçons apprises depuis lors ont été prises en compte par les concepteurs de nouveaux langages, mais C++ est lié par des exigences de compatibilité descendante, ce qui rend très difficile de résoudre un problème aussi fondamental dans le langage.
La définition de classe doit être suffisante pour que le compilateur produise une disposition identique en mémoire partout où vous avez utilisé un objet de la classe. Par exemple, étant donné quelque chose comme:
class X {
int a;
public:
int b;
};
Le compilateur aura généralement a
à l'offset 0 et b
à l'offset 4
. Si le compilateur voyait cela simplement:
class X {
public:
int b;
};
Il "penserait" que b
devrait être à l'offset 0 au lieu de l'offset 4. Lorsque le code utilisant cette définition assignée à b
, le code utilisant la première définition verrait a
get modifié, et vice versa.
La façon habituelle de minimiser les effets des modifications apportées aux parties privées de la classe est généralement appelée l'idiome pimpl (à propos duquel je suis sûr que Google peut donner beaucoup d'informations).
Il y a très probablement plusieurs raisons. Bien que les membres privés ne soient pas accessibles par la plupart des autres classes, ils peuvent toujours être accessibles par les classes d'amis. Donc, au moins dans ce cas, ils peuvent être nécessaires dans l'en-tête, afin que la classe d'amis puisse voir qu'ils existent.
La recompilation des fichiers dépendants peut dépendre de votre structure d'inclusion. L'inclusion des fichiers .h dans un fichier .cpp au lieu d'un autre en-tête peut dans certains cas empêcher de longues chaînes de recompilations.
La principale raison pour laquelle cela est nécessaire est que tout code qui utilise une classe doit connaître les membres de la classe privée afin de générer du code capable de la gérer.
Considérez la classe suivante:
//foo.h
class foo {
char private_member[0x100];
public:
void do_something();
};
qui est utilisé par le code suivant:
#include "foo.h"
void f() {
foo x;
x.do_something();
}
Si nous compilons ce code sur Linux 32 bits en utilisant gcc, avec quelques indicateurs pour simplifier l'analyse, la fonction f
se compile (avec des commentaires):
;allocate 256 bytes on the stack for a foo, plus 4 bytes for a foo*
0: 81 ec 04 01 00 00 sub esp,0x104
;the trivial constructor for foo is elided here
;compute the address of x
6: 8d 44 24 04 lea eax,[esp+0x4]
;pass the foo* to the function being called (the implicit first argument, this)
a: 89 04 24 mov DWORD PTR [esp],eax
;call x.do_something()
d: e8 fc ff ff ff call e <_Z1fv+0xe>
;deallocate the stack space used for this function
12: 81 c4 04 01 00 00 add esp,0x104
;return
18: c3 ret
Il y a deux choses importantes ici:
sizeof(foo)
afin d'allouer la quantité correcte d'espace pour cela.Essentiellement, alors que le programmeur n'a pas besoin de connaître l'implémentation d'une classe pour l'utiliser, le compilateur le fait. Les concepteurs C++ auraient pu permettre aux membres de classe privés d'être inconnus du code client en introduisant certains niveaux d'indirection, mais cela aurait de sérieuses implications en termes de performances dans certains cas. Au lieu de cela, le programmeur peut décider d'implémenter cette indirection lui-même (via le idiome pImpl , par exemple) s'il décide que le compromis en vaut la peine.
Le problème sous-jacent est que le fichier d'en-tête en C++ doit contenir les informations dont le compilateur a besoin, mais est également utilisé comme référence pour un utilisateur humain d'une classe.
En tant qu'utilisateur humain d'une classe, je ne me soucie pas de beaucoup de choses. Les champs privés en sont un, les implémentations en ligne de fonctions et de méthodes en sont un autre. Le compilateur d'autre part se soucie beaucoup.
Des langages plus modernes comme Swift résolvent cela en utilisant un peu plus de temps CPU: il n'y a qu'un seul fichier stocké en permanence, et c'est le fichier source. Le compilateur crée en arrière-plan quelque chose comme un fichier d'en-tête contenant tout ce dont il a besoin pour compiler d'autres fichiers sources en utilisant la classe. Et l'idée permet de montrer à l'utilisateur humain ce qui serait en C++ le fichier d'en-tête, avec seulement les choses incluses que l'utilisateur veut, ce ne sont pas des méthodes privées, mais avec tous les commentaires pour les types, les constantes, les méthodes, etc. présents. Exactement ce que l'utilisateur humain veut. (Le Xcode IDE montre en option comment les méthodes sont appelées à partir d'un langage complètement différent).