web-dev-qa-db-fra.com

Pourquoi devons-nous mettre des membres privés dans les en-têtes?

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:

  • Il encombre l'en-tête de l'utilisateur
  • Il force la recompilation de toutes les bibliothèques clientes chaque fois que les internes sont modifiés

Y a-t-il une raison conceptuelle derrière cette exigence? Est-ce seulement pour faciliter le travail du compilateur?

67
Simon Bergot

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 .

Mise à jour

En effet, ceci (ou plus généralement, la distinction en-tête/fichier source et #includes) 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.

75
Péter Török

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).

17
Jerry Coffin

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.

3
thorsten müller

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:

  • Le code pour f() doit connaître sizeof(foo) afin d'allouer la quantité correcte d'espace pour cela.
  • Il n'y a aucun appel au constructeur de foo. C'est parce que le constructeur est trivial, mais il est impossible de savoir si foo a un constructeur trivial sans connaître ses membres de classe privés.

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.

2
Chris

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).

0
gnasher729