Façons d'organiser l'interface et l'implémentation en C ++


12

J'ai vu qu'il existe plusieurs paradigmes différents en C ++ concernant ce qui va dans le fichier d'en-tête et ce qui est dans le fichier cpp. AFAIK, la plupart des gens, en particulier ceux issus du C, font:

foo.h

 class foo {
 private:
     int mem;
     int bar();
 public:
     foo();
     foo(const foo&);
     foo& operator=(foo);
     ~foo();
 }

foo.cpp

 #include foo.h
 foo::bar() { return mem; }
 foo::foo() { mem = 42; }
 foo::foo(const foo& f) { mem = f.mem; }
 foo::operator=(foo f) { mem = f.mem; }
 foo::~foo() {}
 int main(int argc, char *argv[]) { foo f; }

Cependant, mes professeurs enseignent généralement le C ++ à des débutants comme celui-ci:

foo.h

 class foo {
 private:
     int mem;
     int bar() { return mem; }
 public:
     foo() { mem = 42; }
     foo(const foo& f) { mem = f.mem; }
     foo& operator=(foo f) { mem = f.mem; }
     ~foo() {}
 }

foo.cpp

 #include foo.h
 int main(int argc, char* argv[]) { foo f; }
 // other global helper functions, DLL exports, and whatnot

Originaire de Java, je suis également toujours resté sur cette deuxième voie pour plusieurs raisons, comme le fait que je n'ai à changer quelque chose au même endroit que si les noms d'interface ou de méthode changent, que j'aime les différentes indentations des choses dans les classes quand je regardez leur implémentation, et que je trouve les noms plus lisibles par foorapport à foo::foo.

Je veux collecter des avantages et des inconvénients dans les deux cas. Peut-être qu'il y a même encore d'autres façons?

Un inconvénient de ma voie est bien sûr la nécessité de faire des déclarations prévisionnelles occasionnelles.


2
foo.cppn'a plus rien à voir avec votre fooclasse et devrait être laissé vide (peut-être mais #includepour rendre votre système de construction heureux).
Benjamin Bannier

2
Vos conférenciers sont fous.
Courses de légèreté en orbite du

Réponses:


16

Alors que la deuxième version est plus facile à écrire, elle mélange l'interface avec l'implémentation.

Les fichiers source qui incluent des fichiers d'en-tête doivent être recompilés chaque fois que les fichiers d'en-tête sont modifiés. Dans la première version, vous ne modifiez le fichier d'en-tête que si vous devez modifier l'interface. Dans la deuxième version, vous changeriez le fichier d'en-tête si vous devez changer l'interface ou l'implémentation.

En plus de ne pas exposer les détails de l'implémentation , vous obtiendrez une recompilation inutile avec la deuxième version.


1
+1 Mon profileur n'instrumente pas le code placé dans les fichiers d'en-tête - c'est aussi une raison valable.
Eugene

Si vous voyez ma réponse à cette question programmers.stackexchange.com/questions/4573/… vous verrez comment cela dépend beaucoup de la sémantique de la classe, c'est-à-dire de ce qui sera utilisé (en particulier s'il s'agit d'une partie exposée de votre interface utilisateur et combien d'autres classes de votre système l'utilisent directement).
CashCow

3

Je l'ai fait la deuxième fois en 1993-1995. Il a fallu quelques minutes pour recompiler une petite application avec 5 à 10 fonctions / fichiers (sur ce même PC 486 ... et non, je ne connaissais pas non plus les cours, j'avais seulement 14-15 ans et il n'y avait pas d'Internet ) .

Donc, ce que vous enseignez aux débutants et ce que vous utilisez professionnellement est des techniques très différentes, en particulier en C ++.

Je pense que la comparaison entre C ++ et une voiture F1 est appropriée. Vous ne mettez pas les débutants dans une voiture de F1 (qui ne démarre même pas à moins que vous ne préchauffiez le moteur à 80-95 degrés celcius).

N'enseignez pas le C ++ comme première langue. Vous devez être suffisamment expérimenté pour savoir pourquoi l'option 2 est pire que l'option 1 en général, savoir un peu ce que signifie la compilation / liaison statique et comprendre ainsi pourquoi C ++ la préfère en premier.


Cette réponse serait encore meilleure si vous développiez un peu la compilation / liaison statique (je ne le savais pas à l'époque!)
Felix Dombek

2

La deuxième méthode est ce que j'appellerais une classe totalement intégrée. Vous écrivez une définition de classe, mais tout votre code l'utilisant ne fera que l'intégrer au code.

Oui, le compilateur décide quand se connecter et quand ne pas le faire ... Dans ce cas, vous aidez le compilateur à prendre une décision, et vous finirez par générer réellement moins de code et potentiellement plus rapidement.

Cet avantage est susceptible de l'emporter sur le fait que si vous modifiez l'implémentation d'une fonction, vous devez reconstruire toutes les sources qui l'utilisent. Dans la nature légère de la classe, vous ne modifierez pas l'implémentation. Si vous ajoutez une nouvelle méthode, vous devrez quand même modifier l'en-tête.

Cependant, à mesure que votre classe devient plus complexe, même en ajoutant une boucle, l'avantage de le faire de cette façon diminue.

Il a encore ses avantages, notamment:

  • S'il s'agit d'un code "à fonctionnalité commune", vous pouvez simplement inclure l'en-tête et l'utiliser à partir de plusieurs projets sans avoir à créer de lien avec une bibliothèque qui contient sa source.

L'inconvénient de l'inlining devient un problème lorsqu'il signifie que vous devez introduire des détails d'implémentation dans votre en-tête, c'est-à-dire que vous devez commencer à inclure des en-têtes supplémentaires.

Notez que les modèles sont un cas spécial car vous devez à peu près inclure les détails de l'implémentation. Vous pouvez le masquer dans un autre fichier, mais il doit y être. (Il existe une exception à cette règle avec les instanciations, mais en général, vous insérez vos modèles).


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.