Perché i modelli C ++ mi permettono di eludere i tipi incompleti (dichiarazioni anticipate)?

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

Domanda

Ho provato tre iterazioni del seguente semplice programma. Questo è un tentativo altamente semplificato di scrivere una coppia di classi contenitore-iteratore, ma ho riscontrato problemi con tipi incompleti (dichiarazioni anticipate). Ho scoperto che questo era in realtà possibile una volta che ho templatizzato tutto, ma solo se ho effettivamente utilizzato il parametro template! (L'ho capito guardando Codice sparsetable di Google .)

Qualche suggerimento che spiega perché il secondo funziona mentre il terzo no? (So ??perché il primo non funziona: il compilatore deve conoscere il layout di memoria del contenitore.)

Grazie in anticipo.

// 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
È stato utile?

Soluzione

Il primo richiede una definizione di container poiché si sta eseguendo un'operazione di copia. Se definisci il costruttore di iter dopo la definizione di container , staresti bene. Quindi:

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;
}

Il secondo esempio funziona perché non esiste alcuna classe fino a quando non si crea un'istanza nella funzione main . A quel punto tutti i tipi sono definiti. Prova a spostare una delle definizioni dei modelli iter o container dopo main e otterrai un errore.

Il terzo esempio è una specializzazione per int o almeno così appare. Questo dovrebbe essere compilato perché il parametro template per iter non è usato. La sintassi della specializzazione è un po 'fuori. Tuttavia, non esiste un costruttore appropriato, quindi otterrai solo immondizia per x . Inoltre, gli iteratori sono modellati bene dai puntatori. Passare il valore di this non sarà di grande aiuto. Gli iteratori sono in genere richiesti per una sequenza e non per un singolo oggetto. Tuttavia, non c'è nulla che possa impedirti di costruirne uno.

E non è necessario un ; dopo un corpo di funzione.

Altri suggerimenti

È possibile farlo senza modelli definendo iter :: iter () dopo la definizione del contenitore:

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 versione del modello funziona perché quando si istanziano i modelli, entrambe le classi sono completamente definite.

Nel primo caso, si sta tentando di accedere a una funzione membro della classe Container prima che la classe sia stata definita, quindi non funzionerà.

Nel secondo caso, il modello viene istanziato la prima volta che viene utilizzato con un tipo particolare. A quel punto la classe Container è stata definita, in main, e quindi si compila.

Nel terzo caso c'è un riferimento circolare. container usa iter e iter usa container, quindi non può funzionare.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top