Pourquoi les modèles C ++ me permettent-ils de contourner les types incomplets (déclarations en aval)?

StackOverflow https://stackoverflow.com/questions/620378

Question

J'ai essayé trois itérations du programme simple suivant. Il s'agit d'une tentative très simplifiée d'écrire une paire de classes conteneur et itérateur, mais je rencontrais des problèmes avec des types incomplets (déclarations en aval). J'ai découvert que cela était en fait possible une fois que tout a été modélisé - mais seulement si j'avais réellement utilisé le paramètre template! (J'ai compris cela en consultant Code de sparsetable de Google .)

Y a-t-il des indices expliquant pourquoi le second fonctionne alors que le troisième ne fonctionne pas? (Je sais pourquoi le premier ne fonctionne pas - le compilateur doit connaître la structure de la mémoire du conteneur.)

Merci d'avance.

// This doesn't work: invalid use of incomplete type.
#if 0
struct container;
struct iter {
  container &c;
  int *p;
  iter(container &c) : c(c), p(&c.value()) {}
};
struct container {
  int x;
  int &value() { return x; }
  iter begin() { return iter(*this); }
};
int main() {
  container c;
  c.begin();
  return 0;
}
#endif

// This *does* work.
template<typename T> struct container;
template<typename T> struct iter {
  container<T> &c;
  T *p;
  iter(container<T> &c) : c(c), p(&c.value()) {}
};
template<typename T> struct container {
  T x;
  T &value() { return x; }
  iter<T> begin() { return iter<T>(*this); }
};
int main() {
  container<int> c;
  c.begin();
  return 0;
};

// This doesn't work either.
#if 0
template<typename T> struct container;
template<typename T> struct iter {
  container<int> &c;
  int *p;
  iter(container<int> &c) : c(c), p(&c.value()) {}
};
template<typename T> struct container {
  int x;
  int &value() { return x; }
  iter<int> begin() { return iter<int>(*this); }
};
int main() {
  container<int> c;
  c.begin();
  return 0;
}
#endif
Était-ce utile?

La solution

Le premier nécessite une définition de conteneur puisque vous effectuez une opération de copie. Si vous définissez le constructeur de iter après la définition de conteneur , tout ira bien. Donc:

struct container;
struct iter {
  container &c;
  int *p;
  iter(container &c);
};

struct container {
  int x;
  int &value() { return x; }
  iter begin() { return iter(*this); }
};

iter::iter(container &c) : c(c), p(&c.value()) {}

int main() {
  container c;
  c.begin();
  return 0;
}

Le deuxième exemple fonctionne parce qu'il n'y a pas de classe jusqu'à ce que vous en instanciez une dans votre fonction main . À ce moment-là, tous les types sont définis. Essayez de déplacer l’un des modèles iter ou conteneur après l’utilisation principale et vous obtiendrez une erreur.

Le troisième exemple est une spécialisation pour int ou ainsi il apparaît. Cela doit être compilé car le paramètre template pour iter n'est pas utilisé. Vous avez un peu la syntaxe de spécialisation. Cependant, il n'y a pas de constructeur approprié, vous obtiendrez donc des ordures pour x . De plus, les itérateurs sont bien modélisés par les pointeurs. Passer la valeur de ce ne vous sera pas d'une grande aide. Les itérateurs sont généralement requis pour une séquence et non pour un objet individuel. Bien que rien ne puisse vous empêcher d'en construire un.

Et vous n'avez pas besoin d'un ; après un corps de fonction.

Autres conseils

Vous pouvez le faire sans modèles en définissant iter :: iter () après la définition du conteneur:

struct container;

struct iter {
  container &c;
  int *p;
  iter(container &c);
};

struct container {
  int x;
  int &value() { return x; }
  iter begin() { return iter(*this); }
};

iter::iter(container &c)
    : c(c), p(&c.value()) {}

int main() {
  container c;
  c.begin();
  return 0;
}

La version de modèle fonctionne car, lorsque vous instanciez des modèles, les deux classes sont complètement définies.

Dans le premier cas, vous essayez d'accéder à une fonction membre de la classe Container avant que celle-ci ait été définie. Cela ne fonctionnera donc pas.

Dans le second cas, le modèle est instancié lors de sa première utilisation avec un type particulier. À ce stade, la classe Container a été définie, dans main, et est donc compilée.

Dans le troisième cas, il y a une référence circulaire. container utilise iter et iter utilise container, il ne peut donc pas fonctionner.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top