Je cherche la définition du moment où je suis autorisé à faire la déclaration 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 détenue en tant que membre, pour une classe passée à la fonction membre par référence, etc.?
c++
forward-declaration
c++-faq
Igor Oks
la source
la source
Réponses:
Mettez-vous à la place du compilateur: lorsque vous transmettez un type, 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 on l'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 devrait connaître la disposition du type.
En supposant la déclaration suivante suivante.
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:
Déclarez les fonctions ou méthodes qui acceptent / renvoient des types incomplets:
Définissez des fonctions ou des méthodes qui acceptent / renvoient des pointeurs / références au type incomplet (mais sans utiliser ses membres):
Ce que vous ne pouvez pas faire avec un type incomplet:
Utilisez-le comme classe de base
Utilisez-le pour déclarer un membre:
Définir des fonctions ou des méthodes à l'aide de ce type
Utilisez ses méthodes ou champs, essayant en fait de déréférencer une variable de type incomplet
En ce qui concerne les modèles, il n'y a pas de règle absolue: si vous pouvez utiliser un type incomplet comme paramètre de modèle dépend de la façon dont le type est utilisé dans le modèle.
Par exemple,
std::vector<T>
requiert que son paramètre soit un type complet, alors que ceboost::container::vector<T>
n'est pas le cas. Parfois, un type complet n'est requis que si vous utilisez certaines fonctions membres; c'est le casstd::unique_ptr<T>
par exemple.Un modèle bien documenté doit indiquer dans sa documentation toutes les exigences de ses paramètres, y compris s'il doit s'agir de types complets ou non.
la source
La règle principale est que vous ne pouvez déclarer que des classes dont la disposition de la mémoire (et donc les fonctions membres et les membres de données) n'ont pas besoin d'être connues 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.
la source
Lakos fait la distinction entre l'utilisation des classes
Je ne l'ai jamais vu prononcer plus succinctement :)
la source
En plus des pointeurs et des références à des types incomplets, vous pouvez également déclarer des prototypes de fonctions qui spécifient des paramètres et / ou renvoient des valeurs qui sont des types incomplets. Cependant, vous ne pouvez pas définir une fonction dont le paramètre ou le type de retour est incomplet, sauf s'il s'agit d'un pointeur ou d'une référence.
Exemples:
la source
Jusqu'à présent, aucune des réponses ne décrit quand on peut utiliser une déclaration directe d'un modèle de classe. Alors, c'est parti.
Un modèle de classe peut être transmis déclaré comme:
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 qu'un membre est un pointeur ou une référence au type incomplet dans un autre modèle de classe:
Déclarez qu'un membre est un pointeur ou une référence à l'une de ses instanciations incomplètes:
Déclarez des modèles de fonction ou des modèles de fonction membre qui acceptent / renvoient des types incomplets:
Déclarez des fonctions ou des fonctions membres qui acceptent / renvoient une de ses instanciations incomplètes:
Définissez des modèles de fonction ou des modèles de fonction membre qui acceptent / renvoient des pointeurs / références au type incomplet (mais sans utiliser ses membres):
Définissez 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):
Utilisez-le comme classe de base d'une autre classe de modèle
Utilisez-le pour déclarer un membre d'un autre modèle de classe:
Définissez des modèles de fonction ou des méthodes à l'aide de ce type
Ce que vous ne pouvez pas faire avec un type incomplet:
Utiliser l'une de ses instanciations comme classe de base
Utilisez l'une de ses instanciations pour déclarer un membre:
Définir des fonctions ou des méthodes à l'aide de l'une de ses instanciations
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
Créer des instanciations explicites du modèle de classe
la source
X
etX<int>
est exactement la même, et seule la syntaxe de déclaration directe diffère de quelque manière que ce soit, avec toutes les lignes de votre réponse sauf une équivalant à prendre simplement Luc ets/X/X<int>/g
? Est-ce vraiment nécessaire? Ou ai-je manqué un petit détail différent? C'est possible, mais j'ai comparé visuellement plusieurs fois et je n'en vois aucun ...Dans le fichier dans lequel vous n'utilisez que le pointeur ou la référence à une classe, et aucune fonction membre / membre ne doit être invoquée par le biais de ce pointeur / référence.
avec
class Foo;
// déclaration avantNous pouvons déclarer des données membres 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 données statiques membres de type Foo. En effet, les membres de données statiques sont définis en dehors de la définition de classe.
la source
J'écris ceci comme une réponse distincte plutôt que comme un 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 renvoyez 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 à leur tour connaître que par le biais d'une déclaration directe.
Lorsque vous renvoyez un type incomplet,
X f2();
vous dites que votre appelant doit avoir la spécification de type complète de X. Il en a besoin pour créer le LHS ou l'objet temporaire sur le site de l'appel.De même, si vous acceptez un type incomplet, l'appelant doit avoir construit l'objet qui est le paramètre. Même si cet objet 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:
Je pense qu'il y a un principe important selon lequel un en-tête doit fournir suffisamment d'informations pour l'utiliser sans une dépendance nécessitant d'autres en-têtes. Cela signifie que l'en-tête doit 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 le comportement souhaité . Au lieu d'utiliser la compilation conditionnelle, vous pourriez avoir une exigence bien documentée pour qu'ils fournissent 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 modèle où vous n'êtes explicitement PAS censé les instancier, mentionnées juste pour que quelqu'un ne devienne pas sarcastique avec moi.
la source
I disagree with Luc Touraille's answer
Alors écrivez-lui un commentaire, y compris un lien vers un article de blog si vous avez besoin de la longueur. Cela ne répond pas à la question posée. Si tout le monde pensait que des questions sur le fonctionnement de X justifiaient des réponses en désaccord avec X ou en débattant des limites dans lesquelles nous devrions restreindre notre liberté d'utiliser X - nous n'aurions presque pas de vraies réponses.La règle générale que je respecte est de ne pas inclure de fichier d'en-tête, sauf si je le dois. Donc, sauf si je stocke l'objet d'une classe en tant que variable membre de ma classe, je ne l'inclurai pas, j'utiliserai simplement la déclaration directe.
la source
Tant que vous n'avez pas besoin de la définition (pensez aux pointeurs et aux références), vous pouvez vous en tirer avec des déclarations avancées. C'est pourquoi la plupart du temps vous les voyez dans les en-têtes alors que les fichiers d'implémentation tirent généralement l'en-tête pour la ou les définitions appropriées.
la source
Vous souhaiterez généralement utiliser la déclaration directe 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 de classes déclarées dans le fichier d'en-tête car C ++ ne connaît pas encore la définition de cette classe à ce stade. C'est la logique que vous devez déplacer dans les fichiers .cpp, mais si vous utilisez des fonctions de modèle, vous devez les réduire à la partie qui utilise le modèle et déplacer cette fonction dans l'en-tête.
la source
Supposons que la déclaration avant obtiendra votre code à compiler (obj est créé). La liaison cependant (création d'exe) ne réussira que si les définitions sont trouvées.
la source
class A; class B { A a; }; int main(){}
et faites-moi savoir comment cela se passe. Bien sûr, il ne se compilera pas. Toutes les bonnes réponses ici expliquent pourquoi et les contextes précis et limités dans lesquels la déclaration à terme est valable. Vous avez plutôt écrit ceci sur quelque chose de totalement différent.Je veux juste ajouter une chose importante que vous pouvez faire avec une classe renvoyée non mentionnée dans la réponse de Luc Touraille.
Ce que vous pouvez faire avec un type incomplet:
Définissez des fonctions ou des méthodes qui acceptent / renvoient des pointeurs / références au type incomplet et transfèrent ces pointeurs / références à une autre fonction.
Un module peut passer à travers un objet d'une classe déclarée directe vers un autre module.
la source
Comme, Luc Touraille a déjà très bien expliqué où utiliser et ne pas utiliser la déclaration directe de la classe.
J'ajouterai simplement à cela pourquoi nous devons l'utiliser.
Nous devrions utiliser la déclaration Forward dans la mesure du possible pour éviter l'injection de dépendance indésirable.
Comme
#include
les fichiers d'en-tête sont ajoutés sur plusieurs fichiers, par conséquent, si nous ajoutons un en-tête dans un autre fichier d'en-tête, cela ajoutera une injection de dépendance indésirable dans diverses parties du code source, ce qui peut être évité en ajoutant#include
dans la.cpp
mesure du possible un en-tête dans des fichiers plutôt qu'en ajoutant à un autre fichier d'en-tête et utilisez la déclaration de classe vers l'avant dans la mesure du possible dans les.h
fichiers d' en-tête .la source