Question Quand puis-je utiliser une déclaration forward?


Je cherche la définition de quand je suis autorisé à faire la déclaration avant d'une classe dans le dossier d'en-tête d'une autre classe:

Suis-je autorisé à le faire pour une classe de base, pour une classe tenue en tant que membre, pour une classe transmise à une fonction membre par référence, etc.?


514
2018-02-16 15:31


origine


Réponses:


Mettez-vous dans la position du compilateur: lorsque vous transférez 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 on appelle ça 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 avant suivante.

class X;

Voici ce que vous pouvez et ne pouvez pas faire.

Ce que vous pouvez faire avec un type incomplet:

  • Déclarer qu'un membre est un pointeur ou une référence au type incomplet:

    class Foo {
        X *pt;
        X &pt;
    };
    
  • Déclarer fonctions ou méthodes qui acceptent / retournent des types incomplets:

    void f1(X);
    X    f2();
    
  • Définir fonctions ou 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 {} // compiler error!
    
  • Utilisez-le pour déclarer un membre:

    class Foo {
        X m; // compiler error!
    };
    
  • Définir fonctions ou méthodes utilisant ce type

    void f1(X x) {} // compiler error!
    X    f2()    {} // compiler error!
    
  • Utilisez ses méthodes ou champs, en essayant en fait de déréférencer une variable avec un type incomplet

    class Foo {
        X *m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    

En ce qui concerne les modèles, il n'y a pas de règle absolue: le fait de pouvoir utiliser un type incomplet en tant que 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> nécessite son paramètre pour être un type complet, tandis que boost::container::vector<T> ne fait pas. Parfois, un type complet est requis uniquement si vous utilisez certaines fonctions membres; c'est le cas pour std::unique_ptr<T>, par exemple.

Un modèle bien documenté devrait indiquer dans sa documentation toutes les exigences de ses paramètres, y compris s'ils doivent être complets ou non.


838
2018-02-16 16:24



La règle principale est que vous ne pouvez déclarer que des classes dont la mise en forme 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 retransmettez.

Cela exclurait les classes de base et tout sauf les classes utilisées via des références et des pointeurs.


37
2018-02-16 15:35



Lakos distingue entre l'utilisation de classe

  1. in-name-only (pour lequel une déclaration à terme est suffisante) et
  2. en taille (pour lequel la définition de classe est nécessaire).

Je ne l'ai jamais vu prononcé plus succinctement :)


30
2017-07-21 07:44



Outre les pointeurs et les références aux types incomplets, vous pouvez également déclarer des prototypes de fonction qui spécifient des paramètres et / ou des valeurs de retour qui sont des types incomplets. 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

24
2018-02-16 16:03



Aucune des réponses à ce jour ne décrit quand on peut utiliser une déclaration forward d'un template de classe. Alors, ça va.

Un modèle de classe peut être transmis comme:

template <typename> struct X;

En suivant la structure du 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 <typename T>
    class Foo {
        X<T>* ptr;
        X<T>& ref;
    };
    
  • Déclarez qu'un membre est un pointeur ou une référence à l'une de ses instanciations incomplètes:

    class Foo {
        X<int>* ptr;
        X<int>& ref;
    };
    
  • Déclarez des modèles de fonction ou des modèles de fonctions membres qui acceptent / renvoient des types incomplets:

    template <typename T>
       void      f1(X<T>);
    template <typename T>
       X<T>    f2();
    
  • Déclarez les fonctions ou les fonctions membres qui acceptent / retournent une de ses instanciations incomplètes:

    void      f1(X<int>);
    X<int>    f2();
    
  • Définir des modèles de fonction ou des modèles de fonctions membres qui acceptent / renvoient des pointeurs / références au type incomplet (mais sans utiliser ses membres):

    template <typename T>
       void      f3(X<T>*, X<T>&) {}
    template <typename T>
       X<T>&   f4(X<T>& in) { return in; }
    template <typename T>
       X<T>*   f5(X<T>* in) { return in; }
    
  • Définir des fonctions ou des méthodes qui acceptent / retournent des pointeurs / références à l'une de ses instanciations incomplètes (mais sans utiliser ses membres):

    void      f3(X<int>*, X<int>&) {}
    X<int>&   f4(X<int>& in) { return in; }
    X<int>*   f5(X<int>* in) { return in; }
    
  • Utilisez-le comme une classe de base d'une autre classe de modèle

    template <typename T>
    class Foo : X<T> {} // OK as long as X is defined before
                        // Foo is instantiated.
    
    Foo<int> a1; // Compiler error.
    
    template <typename T> struct X {};
    Foo<int> a2; // OK since X is now defined.
    
  • Utilisez-le pour déclarer un membre d'un autre modèle de classe:

    template <typename T>
    class Foo {
        X<T> m; // OK as long as X is defined before
                // Foo is instantiated. 
    };
    
    Foo<int> a1; // Compiler error.
    
    template <typename T> struct X {};
    Foo<int> a2; // OK since X is now defined.
    
  • Définir modèles de fonction ou méthodes utilisant ce type

    template <typename T>
      void    f1(X<T> x) {}    // OK if X is defined before calling f1
    template <typename T>
      X<T>    f2(){return X<T>(); }  // OK if X is defined before calling f2
    
    void test1()
    {
       f1(X<int>());  // Compiler error
       f2<int>();     // Compiler error
    }
    
    template <typename T> struct X {};
    
    void test2()
    {
       f1(X<int>());  // OK since X is defined now
       f2<int>();     // OK since X is defined now
    }
    

Ce que vous ne pouvez pas faire avec un type incomplet:

  • Utilisez l'une de ses instanciations en tant que classe de base

    class Foo : X<int> {} // compiler error!
    
  • Utilisez l'une de ses instanciations pour déclarer un membre:

    class Foo {
        X<int> m; // compiler error!
    };
    
  • Définir fonctions ou méthodes utilisant l'une de ses instanciations

    void      f1(X<int> x) {}            // compiler error!
    X<int>    f2() {return X<int>(); }   // compiler error!
    
  • Utilisez les méthodes ou champs d'une de ses instanciations, en essayant en fait de déréférencer une variable avec un type incomplet

    class Foo {
        X<int>* m;            
        void method()            
        {
            m->someMethod();      // compiler error!
            int i = m->someField; // compiler error!
        }
    };
    
  • Créer des instanciations explicites du modèle de classe

    template struct X<int>;
    

9
2017-10-31 05:00



Dans le fichier dans lequel vous utilisez uniquement Pointer ou Référence à une classe. Et aucune fonction membre / membre ne doit être invoquée en pensant à ces Pointer / référence.

avec class Foo;// transmettre la 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.


5
2018-02-16 15:40



Tant que vous n'avez pas besoin de la définition (pensez aux pointeurs et aux références), vous pouvez vous débarrasser des déclarations anticipées. C'est pourquoi la plupart du temps vous les verriez dans les en-têtes alors que les fichiers d'implémentation extraient généralement l'en-tête pour les définitions appropriées.


3
2018-02-16 15:34