97 votes

Doit-on utiliser de l'avant déclarations au lieu de comprend dans la mesure du possible?

Chaque fois qu'une déclaration de classe utilise une autre classe seulement comme des pointeurs, est-il judicieux d'utiliser une classe de l'avant de la déclaration au lieu de notamment la headerfile pour préventivement éviter les problèmes de dépendances circulaires? donc, au lieu d'avoir:

//file C.h
#include "A.h"
#include "B.h"

class C{
    A* a;
    B b;
    ...
};

faire ceci à la place:

//file C.h
#include "B.h"

class A;

class C{
    A* a;
    B b;
    ...
};


//file C.cpp
#include "C.h"
#include "A.h"
...

Est-il une raison de ne pas le faire dans la mesure du possible?

71voto

Luchian Grigore Points 136646

Les énoncés de la déclaration de la méthode est presque toujours la meilleure. (Je ne peux pas penser à une situation où, y compris un fichier où vous pouvez utiliser une déclaration anticipée est mieux, mais je ne vais pas dire que c'est toujours mieux, juste au cas où).

Il n'y a pas des inconvénients à l'avant-déclarer des classes, mais je ne peux penser à quelques inconvénients, y compris pour les en-têtes inutilement:

  • plus le temps de compilation, puisque toutes les unités de traduction, y compris C.h comprendra également A.h, bien qu'ils ne pourraient pas besoin.

  • peut-être y compris d'autres en-têtes, vous n'avez pas besoin indirectement

  • polluer l'unité de traduction avec les symboles que vous n'avez pas besoin

  • vous pourriez avoir besoin de recompiler les fichiers sources qui comprennent que l'en-tête en cas de changement (@PeterWood)

45voto

Alok Save Points 115848

Oui, à l'aide de sa déclaration est toujours la meilleure.

Quelques-uns des avantages qu'ils fournissent sont:

  • Réduit le temps de compilation.
  • Noms sans polluer.
  • (Dans certains cas)peut réduire la taille de vos généré les fichiers binaires.
  • La Recompilation du temps peut être considérablement réduit.
  • Éviter les clash de préprocesseur noms.
  • La mise en œuvre de PIMPL Idiome offrant ainsi un moyen de cacher l'implémentation de l'interface.

Cependant, Avant la déclaration d'une classe rend cette classe particulière d'un type Incomplète et que sévèrement restreint les opérations que vous pouvez effectuer sur le type Incomplète.
Vous ne pouvez pas effectuer toutes les opérations qui aurait besoin du compilateur pour connaître la disposition de la classe.

Avec type Incomplète, vous pouvez:

  • Déclarer un membre à un pointeur ou une référence au type incomplète.
  • De déclarer des fonctions ou des méthodes qui accepte/retour incomplet types.
  • Définir des fonctions ou des méthodes qui accepte de rendement/pointeurs/références pour le type incomplète (mais sans l'aide de ses membres).

Avec type Incomplète vous ne pouvez pas:

  • L'utiliser comme une classe de base.
  • L'utiliser pour déclarer un membre.
  • Définir des méthodes ou des fonctions à l'aide de ce type.

24voto

Matthieu M. Points 101624

Est-il une raison de ne pas le faire dans la mesure du possible?

La commodité.

Si vous savez à l'avance de phase que tout utilisateur de ce fichier d'en-tête sera nécessairement besoin d'inclure également la définition de l' A de ne rien faire (ou peut-être la plupart du temps). Ensuite, il est commode de simplement inclure une fois pour toutes.

C'est plutôt un sujet délicat, comme trop libéral utilisation de cette règle de pouce aura un rendement proche uncompilable code. Notez que Boost aborde le problème différemment en fournissant certaines "commodité" en-têtes des paquets, un couple de fermer les fonctionnalités de l'ensemble.

13voto

anatolyg Points 8076

Une affaire dans laquelle vous ne voulez pas avoir des déclarations de l'avant, c'est quand ils sont eux-mêmes difficiles. Cela peut se produire si certains de vos classes sont basées sur des modèles, comme dans l'exemple suivant:

// Forward declarations
template <typename A> class Frobnicator;
template <typename A, typename B, typename C = Frobnicator<A> > class Gibberer;

// Alternative: more clear to the reader; more stable code
#include "Gibberer.h"

// Declare a function that does something with a pointer
int do_stuff(Gibberer<int, float>*);

L'avant-déclarations sont les mêmes que la duplication de code: si le code a tendance à changer beaucoup de choses, vous devez la changer dans 2 places ou plus à chaque fois, et ce n'est pas bon.

9voto

Blue Demon Points 41

Doit-on utiliser de l'avant déclarations au lieu de comprend dans la mesure du possible?

Non, explicite sa déclaration ne devrait pas être considérée comme une ligne directrice générale. Avant les déclarations sont essentiellement des copier-collé, ou mal orthographié code, qui, dans le cas où vous trouvez un bug, faut fixes partout la déclaration sont utilisés. Cela peut être source d'erreurs.

Pour éviter les décalages entre le "avant" les déclarations et les définitions, mettre des déclarations dans un fichier d'en-tête et inclure que les fichiers d'en-tête à la fois la définition et à la déclaration, à l'aide de fichiers source.

Dans ce cas particulier, cependant, où opaque classe est à l'avant-déclarée, cette déclaration peut être acceptable d'utiliser, mais en général, à "utiliser la déclaration au lieu de comprend la mesure du possible", comme le titre de ce fil dit, peut être très risqué.

Voici quelques exemples de "invisible risques" en matière de déclaration (invisible risques = déclaration de l'inadéquation qui ne sont pas détectées par le compilateur ou l'éditeur de liens):

  • Explicite de l'avant les déclarations de symboles représentant les données peuvent être dangereux, parce que de telles déclarations peuvent nécessiter de connaissance de l'empreinte (taille) du type de données.

  • Explicite de l'avant les déclarations de symboles représentant les fonctions peuvent également être dangereux, comme les types de paramètres et le nombre de paramètres.

L'exemple ci-dessous illustre, par exemple, deux dangereux en avant les déclarations de données, ainsi que d'une fonction:

Fichier un.c:

#include <iostream>
char data[128][1024];
extern "C" void function(short truncated, const char* forgotten) {
  std::cout << "truncated=" << std::hex << truncated
            << ", forgotten=\"" << forgotten << "\"\n";
}

Fichier b.c:

#include <iostream>
extern char data[1280][1024];           // 1st dimension one decade too large
extern "C" void function(int tooLarge); // Wrong 1st type, omitted 2nd param

int main() {
  function(0x1234abcd);                         // In worst case: - No crash!
  std::cout << "accessing data[1270][1023]\n";
  return (int) data[1270][1023];                // In best case:  - Boom !!!!
}

Compiler le programme avec g++ 4.7.1:

> g++ -Wall -pedantic -ansi a.c b.c

Note: Invisible danger, puisque g++ donne pas de compilateur ou l'éditeur de liens erreurs/avertissements
Remarque: en Omettant extern "C" conduit à une erreur de couplage pour function() en raison du c++ name mangling.

L'exécution du programme:

> ./a.out
truncated=abcd, forgotten="♀♥♂☻"
accessing data[1270][1023]
Segmentation fault

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X