Question

J'essaie d'implémenter une méthode pour un arbre binaire qui renvoie un flux. Je souhaite utiliser le flux renvoyé par une méthode pour afficher l'arborescence à l'écran ou pour enregistrer l'arborescence dans un fichier:

Ces deux méthodes sont dans la classe de l’arbre binaire:

Déclarations:

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

Cette méthode est dans la classe UsingTree:

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

J'ai donc surchargé l'opérateur " < < " de la classe BinaryTree à utiliser: cout < < arbre et ofstream f < < arbre, mais je reçois le message d'erreur suivant: référence indéfinie à `l'opérateur < < (std :: basic_ostream > & amp ;, BinaryTree & amp;) '

P.S. L'arborescence stocke des objets Word (une chaîne avec un entier).

J'espère que vous comprenez mon pauvre anglais. Je vous remercie! Et j'aimerais connaître un bon texte pour les débutants sur STL, qui explique tout ce qui est nécessaire parce que je perds tout mon temps dans des erreurs comme celle-ci.

EDIT: une arborescence de saveToFile () est déclarée: BinaryTree < Word > arbre.

Était-ce utile?

La solution

Le problème est que le compilateur n'essaie pas d'utiliser l'opérateur basé sur un modèle < < que vous avez fourni, mais plutôt une version non basée sur un modèle.

Lorsque vous déclarez un ami dans une classe, vous injectez la déclaration de cette fonction dans la portée englobante. Le code suivant a pour effet de déclarer (et non de définir) une fonction libre prenant un argument non_template_test par référence constante:

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

La même chose se produit avec les classes de modèles, même si dans ce cas, c'est un peu moins intuitif. Lorsque vous déclarez (et ne définissez pas) une fonction amie dans le corps de la classe de modèle, vous déclarez une fonction libre avec les arguments exacts. Notez que vous déclarez une fonction et non une fonction modèle:

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

Ces fonctions libres sont déclarées mais non définies. La partie délicate ici est que ces fonctions libres ne sont pas un modèle, mais des fonctions libres régulières déclarées. Lorsque vous ajoutez la fonction de modèle au mélange, vous obtenez:

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

Lorsque le compilateur atteint la fonction principale, il instancie le modèle template_test de type int et qui déclare la fonction libre void f (template_test < int > const & amp ;) qui n'est pas modélisé. Lorsqu'il trouve l'appel f (t1) , deux symboles f correspondent: le non-modèle f (template_test < int > const & amp;) déclaré (et non défini) lorsque template_test a été instancié et la version basée sur un modèle déclarée et définie à 1 . La version non basée sur un modèle est prioritaire et le compilateur y correspond.

Lorsque l'éditeur de liens tente de résoudre la version de f sans modèle, il ne trouve pas le symbole et échoue donc.

Que pouvons-nous faire? Il y a deux solutions différentes. Dans le premier cas, nous demandons au compilateur de fournir des fonctions non basées sur des modèles pour chaque type d'instanciation. Dans le second cas, nous déclarons la version basée sur un modèle en tant qu'ami. Ils sont légèrement différents, mais dans la plupart des cas équivalents.

Demander au compilateur de générer les fonctions non basées sur un modèle:

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

Ceci a pour effet de créer autant de fonctions libres non modélisées que nécessaire. Lorsque le compilateur trouve la déclaration d'ami dans le modèle test , il trouve non seulement la déclaration, mais également l'implémentation et l'ajoute à la portée englobante.

Faire de la version basée sur un modèle un ami

Pour que le modèle soit un ami, vous devez l'avoir déjà déclaré et indiquer au compilateur que cet ami est en fait un modèle et non une fonction libre non modélisée:

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 & ) {}

Dans ce cas, avant de déclarer f en tant que modèle, nous devons transmettre et déclarer le modèle. Pour déclarer le modèle f , nous devons d'abord déclarer le modèle test . La déclaration de l'ami est modifiée pour inclure les chevrons indiquant que l'élément que nous faisons à un ami est en réalité un modèle et non une fonction libre.

Retour au problème

Pour revenir à votre exemple particulier, la solution la plus simple consiste à ce que le compilateur génère les fonctions pour vous en ajoutant la déclaration de la fonction ami:

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

Avec ce code, vous obligez le compilateur à générer un opérateur non basé sur un modèle < < pour chaque type instancié, ainsi que les délégués de fonction générés sur la méthode dump de le modèle.

Autres conseils

vous n’avez pas besoin de la déclaration de l’opérateur de modèle et vous devez déclarer l’opérateur " ami " pour que votre classe ait accordé l'accès à d'autres classes, dans ce cas std :: cout

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

lecture recommandée: http://www.parashift.com/c++- faq-lite / friends.html

En cas de surcharge de l'opérateur < < , vous souhaitez utiliser une référence 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;
}

Assurez-vous que les définitions complètes des modèles (et pas seulement les prototypes) figurent dans le fichier include (c'est-à-dire .h, .hpp). Les modèles et la compilation séparée ne fonctionnent pas ensemble.

Je ne sais pas quel est l'éditeur de liens utilisé par @Dribeas, mais cela peut certainement amener l'éditeur de liens GNU à donner une erreur de référence non définie.

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