Je cherche la définition de quand je suis autorisé à faire une déclaration en avant d’une classe dans le fichier d’en-tête d’une autre classe:
Suis-je autorisé à le faire pour une classe de base, pour une classe en tant que membre, pour une classe passée à la fonction membre par référence, etc.?
Mettez-vous à la position du compilateur: lorsque vous déclarez un type, tout le compilateur sait que ce type existe; il ne sait rien de sa taille, de ses membres ou de ses méthodes. C’est pourquoi cela s’appelle un type incomplet . Par conséquent, vous ne pouvez pas utiliser le type pour déclarer un membre ou une classe de base, car le compilateur doit connaître la disposition du type.
En supposant la déclaration suivante.
class X;
Voici ce que vous pouvez et ne pouvez pas faire.
Ce que vous pouvez faire avec un type incomplet:
Déclarez qu’un membre est un pointeur ou une référence au type incomplet:
class Foo { X *pt; X &pt; };
Déclarez les fonctions ou méthodes qui acceptent / renvoient des types incomplets:
void f1(X); X f2();
Définir des fonctions ou des méthodes qui acceptent / renvoient des pointeurs / références au type incomplet (mais sans utiliser ses membres):
void f3(X*, X&) {} X& f4() {} X* f5() {}
Ce que vous ne pouvez pas faire avec un type incomplet:
Utilisez-le comme une classe de base
class Foo : X {} // comstackr error!
Utilisez-le pour déclarer un membre:
class Foo { X m; // comstackr error! };
Définir des fonctions ou des méthodes utilisant ce type
void f1(X x) {} // comstackr error! X f2() {} // comstackr error!
Utiliser ses méthodes ou ses champs, en essayant en fait de déréférencer une variable de type incomplet
class Foo { X *m; void method() { m->someMethod(); // comstackr error! int i = m->someField; // comstackr error! } };
En ce qui concerne les modèles, il n’y a pas de règle absolue: si vous pouvez utiliser un type incomplet car un paramètre de modèle dépend de la manière dont le type est utilisé dans le modèle.
Par exemple, std::vector
exige que son paramètre soit un type complet, alors que boost::container::vector
ne le fait pas. Parfois, un type complet est requirejs uniquement si vous utilisez certaines fonctions membres. c’est le cas par exemple de std::unique_ptr
.
Un modèle bien documenté doit indiquer dans sa documentation toutes les exigences de ses parameters, y compris s’il doit être complet ou non.
La règle principale est que vous ne pouvez déclarer que les classes dont la disposition de la mémoire (et donc les fonctions membres et les membres de données) n’a pas besoin d’être connue dans le fichier que vous déclarez.
Cela exclurait les classes de base et tout sauf les classes utilisées via des références et des pointeurs.
Lakos distingue entre l’utilisation de classe
Je ne l’ai jamais vu prononcer plus succinctement 🙂
Outre les pointeurs et les références aux types incomplets, vous pouvez également déclarer des prototypes de fonctions qui spécifient des parameters et / ou renvoient des valeurs incomplètes. Cependant, vous ne pouvez pas définir une fonction ayant un paramètre ou un type de retour incomplet, sauf s’il s’agit d’un pointeur ou d’une référence.
Exemples:
struct X; // Forward declaration of X void f1(X* px) {} // Legal: can always use a pointer void f2(X& x) {} // Legal: can always use a reference X f3(int); // Legal: return value in function prototype void f4(X); // Legal: parameter in function prototype void f5(X) {} // ILLEGAL: *definitions* require complete types
Aucune des réponses à ce jour ne décrit quand on peut utiliser une déclaration directe d’un modèle de classe. Donc, ça va.
Un modèle de classe peut être transféré déclaré comme:
template struct X;
En suivant la structure de la réponse acceptée ,
Voici ce que vous pouvez et ne pouvez pas faire.
Ce que vous pouvez faire avec un type incomplet:
Déclarez un membre comme étant un pointeur ou une référence au type incomplet dans un autre modèle de classe:
template class Foo { X* ptr; X & ref; };
Déclarez qu’un membre est un pointeur ou une référence à l’une de ses instanciations incomplètes:
class Foo { X* ptr; X & ref; };
Déclarez les modèles de fonction ou les modèles de fonction membres qui acceptent / renvoient des types incomplets:
template void f1(X); template X f2();
Déclarez les fonctions ou fonctions membres qui acceptent / renvoient l’une de ses instanciations incomplètes:
void f1(X); X f2();
Définir des modèles de fonction ou des modèles de fonction membres qui acceptent / renvoient des pointeurs / références au type incomplet (mais sans utiliser ses membres):
template void f3(X*, X &) {} template X& f4(X & in) { return in; } template X* f5(X * in) { return in; }
Définir des fonctions ou des méthodes qui acceptent / renvoient des pointeurs / références à l’une de ses instanciations incomplètes (mais sans utiliser ses membres):
void f3(X*, X &) {} X & f4(X & in) { return in; } X * f5(X * in) { return in; }
Utilisez-le comme classe de base d’une autre classe de modèle
template class Foo : X {} // OK as long as X is defined before // Foo is instantiated. Foo a1; // Comstackr error. template struct X {}; Foo a2; // OK since X is now defined.
Utilisez-le pour déclarer un membre d’un autre modèle de classe:
template class Foo { X m; // OK as long as X is defined before // Foo is instantiated. }; Foo a1; // Comstackr error. template struct X {}; Foo a2; // OK since X is now defined.
Définir des modèles de fonction ou des méthodes utilisant ce type
template void f1(X x) {} // OK if X is defined before calling f1 template X f2(){return X (); } // OK if X is defined before calling f2 void test1() { f1(X()); // Comstackr error f2 (); // Comstackr error } template struct X {}; void test2() { f1(X()); // OK since X is defined now f2 (); // OK since X is defined now }
Ce que vous ne pouvez pas faire avec un type incomplet:
Utiliser une de ses instanciations comme classe de base
class Foo : X {} // comstackr error!
Utilisez l’une de ses instanciations pour déclarer un membre:
class Foo { X m; // comstackr error! };
Définir des fonctions ou des méthodes en utilisant l’une de ses instanciations
void f1(X x) {} // comstackr error! X f2() {return X (); } // comstackr error!
Utiliser les méthodes ou les champs d’une de ses instanciations, en essayant en fait de déréférencer une variable de type incomplet
class Foo { X* m; void method() { m->someMethod(); // comstackr error! int i = m->someField; // comstackr error! } };
Créer des instanciations explicites du modèle de classe
template struct X;
Dans le fichier dans lequel vous utilisez uniquement le pointeur ou la référence à une classe.Et aucune fonction membre / membre ne doit être invoquée en pensant à ces points / références.
avec la class Foo;
// faire une déclaration
Nous pouvons déclarer des membres de données de type Foo * ou Foo &.
Nous pouvons déclarer (mais pas définir) des fonctions avec des arguments et / ou des valeurs de retour de type Foo.
Nous pouvons déclarer des membres de données statiques de type Foo. Cela est dû au fait que les membres de données statiques sont définis en dehors de la définition de classe.
Tant que vous n’avez pas besoin de la définition (pointeurs de reflection et références), vous pouvez vous en sortir avec des déclarations anticipées. C’est pourquoi la plupart du temps, vous les voyez dans les en-têtes alors que les fichiers d’implémentation vont généralement chercher l’en-tête pour la ou les définitions appropriées.
La règle générale que je suis n’est pas d’inclure un fichier d’en-tête, sauf si je dois le faire. Donc, à moins que je stocke l’object d’une classe en tant que variable membre de ma classe, je ne l’inclurai pas, j’utiliserai simplement la déclaration en avant.
J’écris ceci comme une réponse distincte plutôt que comme un simple commentaire car je ne suis pas d’accord avec la réponse de Luc Touraille, non pas pour des raisons de légalité, mais pour un logiciel robuste et le danger d’une mauvaise interprétation.
Plus précisément, j’ai un problème avec le contrat implicite de ce que vous attendez des utilisateurs de votre interface.
Si vous retournez ou acceptez des types de référence, vous dites simplement qu’ils peuvent passer par un pointeur ou une référence qu’ils ne peuvent connaître qu’à l’aide d’une déclaration préalable.
Lorsque vous retournez un type incomplet X f2();
alors vous dites que votre appelant doit avoir la spécification de type complète de X. Ils en ont besoin pour créer le LHS ou l’object temporaire sur le site d’appel.
De même, si vous acceptez un type incomplet, l’appelant doit avoir construit l’object qui est le paramètre. Même si cet object a été renvoyé comme un autre type incomplet à partir d’une fonction, le site d’appel a besoin de la déclaration complète. c’est à dire:
class X; // forward for two legal declarations X returnsX(); void XAcceptor(X); XAcepptor( returnsX() ); // X declaration needs to be known here
Je pense qu’il existe un principe important selon lequel un en-tête devrait fournir suffisamment d’informations pour pouvoir être utilisé sans dépendance nécessitant d’autres en-têtes. Cela signifie que l’en-tête devrait pouvoir être inclus dans une unité de compilation sans provoquer d’erreur de compilation lorsque vous utilisez les fonctions qu’il déclare.
Sauf
Si cette dépendance externe est un comportement souhaité . Au lieu d’utiliser une compilation conditionnelle, vous pourriez avoir besoin d’une documentation bien documentée pour fournir leur propre en-tête déclarant X. C’est une alternative à l’utilisation de #ifdefs et peut être un moyen utile d’introduire des simulations ou d’autres variantes.
La distinction importante étant certaines techniques de gabarit dans lesquelles vous ne devez PAS explicitement les instancier, mentionnées simplement pour que quelqu’un ne soit pas pris de court avec moi.
Vous voudrez généralement utiliser la déclaration forward dans un fichier d’en-tête de classes lorsque vous souhaitez utiliser l’autre type (classe) en tant que membre de la classe. Vous ne pouvez pas utiliser les méthodes des classes déclarées en avant dans le fichier d’en-tête car C ++ ne connaît pas encore la définition de cette classe. C’est logique que vous devez vous déplacer dans les fichiers .cpp, mais si vous utilisez des fonctions de modèle, vous devez les réduire uniquement à la partie qui utilise le modèle et déplacer cette fonction dans l’en-tête.
Supposons que la déclaration en avant obtienne votre code à comstackr (obj est créé). Cependant, la création de liens (création d’exe) ne sera réussie que si les définitions sont trouvées.
Je veux juste append une chose importante que vous pouvez faire avec une classe transférée non mentionnée dans la réponse de Luc Touraille.
Ce que vous pouvez faire avec un type incomplet:
Définir des fonctions ou des méthodes qui acceptent / renvoient des pointeurs / références au type incomplet et transmettent ces pointeurs / références à une autre fonction.
void f6(X*) {} void f7(X&) {} void f8(X* x_ptr, X& x_ref) { f6(x_ptr); f7(x_ref); }
Un module peut passer par un object d’une classe déclarée avant vers un autre module.