Domanda

Sto cercando di implementare un metodo per un albero binario che restituisce un flusso. Voglio usare il flusso restituito in un metodo per mostrare l'albero nella schermata o per salvare l'albero in un file:

Questi due metodi sono nella classe dell'albero binario:

Dichiarazione:

void streamIND(ostream&,const BinaryTree<T>*);
friend ostream& operator<<(ostream&,const BinaryTree<T>&);

template <class T>
ostream& operator<<(ostream& os,const BinaryTree<T>& tree) {
    streamIND(os,tree.root);
    return os;
}

template <class T>
void streamIND(ostream& os,Node<T> *nb) {
    if (!nb) return;
    if (nb->getLeft()) streamIND(nb->getLeft());
    os << nb->getValue() << " ";
    if (nb->getRight()) streamIND(nb->getRight());
}

Questo metodo è nella classe UsingTree:

void UsingTree::saveToFile(char* file = "table") {
    ofstream f;
    f.open(file,ios::out);
    f << tree;
    f.close();
}

Quindi ho sovraccaricato l'operatore " < < " della classe BinaryTree da usare: cout < < albero e ofstream f < < albero, ma ricevo il seguente messaggio di errore: riferimento indefinito a `operator < < (std :: basic_ostream > & amp ;, BinaryTree & amp;) '

P.S. L'albero memorizza oggetti Word (una stringa con un int).

Spero che tu capisca il mio povero inglese. Grazie! E mi piacerebbe sapere un buon testo per principianti su STL che spiega tutto il necessario perché spreco tutto il mio tempo in errori come questo.

EDIT: l'albero in saveToFile () è dichiarato: BinaryTree < Parola > albero.

È stato utile?

Soluzione

Il problema è che il compilatore non sta provando a utilizzare l'operatore < < fornito, ma piuttosto una versione non basata su template.

Quando dichiari un amico all'interno di una classe, stai iniettando la dichiarazione di quella funzione nell'ambito incluso. Il codice seguente ha l'effetto di dichiarare (e non definire) una funzione libera che accetta un argomento non_template_test con riferimento costante:

class non_template_test
{
   friend void f( non_template_test const & );
};
// declares here:
// void f( non_template_test const & ); 

Lo stesso succede con le classi template, anche se in questo caso è un po 'meno intuitivo. Quando dichiari (e non definisci) una funzione di amicizia nel corpo della classe template, stai dichiarando una funzione libera con quegli argomenti esatti. Nota che stai dichiarando una funzione, non una funzione modello:

template<typename T>
class template_test
{
    friend void f( template_test<T> const & t );
};
// for each instantiating type T (int, double...) declares:
// void f( template_test<int> const & );
// void f( template_test<double> const & );

int main() {
    template_test<int> t1;
    template_test<double> t2;
}

Queste funzioni libere sono dichiarate ma non definite. La parte difficile qui è che quelle funzioni libere non sono un modello, ma vengono dichiarate normali funzioni libere. Quando aggiungi la funzione template nel mix ottieni:

template<typename T> class template_test {
   friend void f( template_test<T> const & );
};
// when instantiated with int, implicitly declares:
// void f( template_test<int> const & );

template <typename T>
void f( template_test<T> const & x ) {} // 1

int main() {
   template_test<int> t1;
   f( t1 );
}

Quando il compilatore esegue la funzione principale, crea un'istanza del modello template_test con il tipo int e che dichiara la funzione gratuita void f (template_test < int > const & amp ;) che non è modellato. Quando trova la chiamata f (t1) ci sono due simboli f che corrispondono: il non template f (template_test < int > const & amp;) dichiarato (e non definito) quando template_test è stato istanziato e la versione modello che è sia dichiarata che definita in 1 . La versione non basata su modelli ha la precedenza e il compilatore corrisponde.

Quando il linker tenta di risolvere la versione non-template di f non riesce a trovare il simbolo e quindi fallisce.

Cosa possiamo fare? Esistono due diverse soluzioni. Nel primo caso facciamo in modo che il compilatore fornisca funzioni non basate su modelli per ogni tipo di istanza. Nel secondo caso dichiariamo la versione del modello come un amico. Sono leggermente diversi, ma nella maggior parte dei casi equivalenti.

Far compilare al compilatore le funzioni non basate su modelli per noi:

template <typename T>
class test 
{
   friend void f( test<T> const & ) {}
};
// implicitly

Questo ha l'effetto di creare tutte le funzioni libere non basate sul modello necessarie. Quando il compilatore trova la dichiarazione di amicizia all'interno del modello test non solo trova la dichiarazione ma anche l'implementazione e aggiunge sia all'ambito che lo racchiude.

Trasforma la versione in modello in un amico

Per rendere il modello un amico dobbiamo averlo già dichiarato e dire al compilatore che l'amico che vogliamo è in realtà un modello e non una funzione gratuita non basata su modelli:

template <typename T> class test; // forward declare the template class
template <typename T> void f( test<T> const& ); // forward declare the template
template <typename T>
class test {
   friend void f<>( test<T> const& ); // declare f<T>( test<T> const &) a friend
};
template <typename T> 
void f( test<T> const & ) {}

In questo caso, prima di dichiarare f come modello, dobbiamo inoltrare la dichiarazione del modello. Per dichiarare il modello f dobbiamo prima inoltrare il modello test . La dichiarazione di amicizia viene modificata per includere le parentesi angolari che identificano che l'elemento che stiamo creando un amico è in realtà un modello e non una funzione gratuita.

Torna al problema

Tornando al tuo esempio particolare, la soluzione più semplice è che il compilatore generi le funzioni per te incorporando la dichiarazione della funzione amico:

template <typename T>
class BinaryTree {
   friend std::ostream& operator<<( std::ostream& o, BinaryTree const & t ) {
      t.dump(o);
      return o;
   }
   void dump( std::ostream& o ) const;
};

Con quel codice stai forzando il compilatore a generare un operatore non tentato < < per ogni tipo istanziato e che hai generato delegati di funzione sul metodo dump di il modello.

Altri suggerimenti

non hai bisogno della dichiarazione dell'operatore modello e devi dichiarare l'operatore "amico" affinché la tua classe abbia concesso l'accesso ad altre classi, in questo caso std :: cout

friend std::ostream& operator << ( std::ostream& os, BinaryTree & tree )
{
    doStuff( os, tree );
    return os;
}

Lettura consigliata: http://www.parashift.com/c++- faq-lite / friends.html

Quando si sovraccarica l'operatore < < si desidera utilizzare un riferimento const:

template <class T>
std::ostream& operator << (std::ostream& os, const BinaryTree<T>& tree) 
{
    // output member variables here... (you may need to make
    // this a friend function if you want to access private
    // member variables...

    return os;
}

Assicurati che le definizioni complete del modello (e non solo i prototipi) siano nel file include (cioè .h, .hpp) poiché i modelli e la compilazione separata non funzionano insieme.

Non so quale linker @Dribeas sta usando, ma questo può sicuramente causare al linker GNU un errore di riferimento indefinito.

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