Pregunta

Estoy intentando implementar un método para un árbol binario que devuelve una secuencia. Quiero usar la secuencia devuelta en un método para mostrar el árbol en la pantalla o para guardar el árbol en un archivo:

Estos dos métodos están en la clase del árbol binario:

Declaraciones:

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

Este método está en la clase UsingTree:

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

Así que sobrecargué al operador " < < " de la clase BinaryTree para usar: cout < < árbol y ofstream f < < árbol, pero recibo el siguiente mensaje de error: referencia no definida a `operator < < (std :: basic_ostream > & amp ;, BinaryTree & amp;) '

P.S. El árbol almacena objetos de Word (una cadena con un int).

Espero que entiendas mi pobre inglés. ¡Gracias! Y me gustaría saber un buen texto para principiantes sobre STL que explique todo lo necesario porque pierdo todo mi tiempo en errores como este.

EDITAR: el árbol en saveToFile () se declara: BinaryTree < Palabra > árbol.

¿Fue útil?

Solución

El problema es que el compilador no está tratando de usar el operador con plantilla < < que proporcionó, sino una versión sin plantilla.

Cuando declara a un amigo dentro de una clase, está inyectando la declaración de esa función en el ámbito adjunto. El siguiente código tiene el efecto de declarar (y no definir) una función libre que toma un argumento non_template_test por referencia constante:

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

Lo mismo sucede con las clases de plantilla, incluso si en este caso es un poco menos intuitivo. Cuando declara (y no define) una función de amigo dentro del cuerpo de la clase de plantilla, está declarando una función libre con los argumentos exactos. Tenga en cuenta que está declarando una función, no una función de plantilla:

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

Esas funciones libres están declaradas pero no definidas. La parte difícil aquí es que esas funciones gratuitas no son una plantilla, sino que se declaran funciones libres regulares. Cuando agrega la función de plantilla a la mezcla, obtiene:

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

Cuando el compilador llega a la función principal, crea una instancia de la plantilla template_test con el tipo int y eso declara la función libre void f (template_test < int > const & amp ;) que no tiene plantilla. Cuando encuentra la llamada f (t1) , hay dos símbolos f que coinciden: la no-plantilla f (template_test < int > const & amp;) declarado (y no definido) cuando se creó una instancia de template_test y la versión de plantilla que se declaró y definió en 1 . La versión sin plantilla tiene prioridad y el compilador la coincide.

Cuando el vinculador intenta resolver la versión sin plantilla de f , no puede encontrar el símbolo y, por lo tanto, falla.

¿Qué podemos hacer? Hay dos soluciones diferentes. En el primer caso, hacemos que el compilador proporcione funciones sin plantilla para cada tipo de creación de instancias. En el segundo caso declaramos la versión de plantilla como amigo. Son sutilmente diferentes, pero en la mayoría de los casos son equivalentes.

Hacer que el compilador genere las funciones sin plantilla para nosotros:

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

Esto tiene el efecto de crear tantas funciones libres sin plantilla como sea necesario. Cuando el compilador encuentra la declaración de amigo dentro de la plantilla test , no solo encuentra la declaración sino también la implementación y agrega ambos al ámbito adjunto.

Hacer de la versión con plantilla un amigo

Para hacer que la plantilla sea un amigo, debemos tenerla ya declarada y decirle al compilador que el amigo que queremos es en realidad una plantilla y no una función gratuita sin plantilla:

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

En este caso, antes de declarar f como plantilla, debemos reenviar declarar la plantilla. Para declarar la plantilla f primero debemos reenviar declarar la plantilla test . La declaración de amistad se modifica para incluir los corchetes angulares que identifican que el elemento que estamos creando es una plantilla y no una función gratuita.

Volver al problema

Volviendo a su ejemplo particular, la solución más simple es hacer que el compilador genere las funciones para usted al insertar la declaración de la función de amigo:

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 ese código, está forzando al compilador a generar un operador no templado < < para cada tipo instanciado, y esa función generada delega en el método de dump de la plantilla.

Otros consejos

no necesita la declaración del operador de plantilla y tiene que declarar al operador " amigo " para que su clase haya otorgado acceso a otras clases, en este caso std :: cout

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

lectura recomendada: http://www.parashift.com/c++- faq-lite / friends.html

Al sobrecargar el operador < < desea utilizar una referencia constante:

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

Asegúrese de que las definiciones de la plantilla completa (y no solo los prototipos) estén en el archivo de inclusión (es decir, .h, .hpp), ya que las plantillas y la compilación por separado no funcionan juntas.

No sé qué está usando el enlazador @Dribeas, pero esto definitivamente puede hacer que el enlazador GNU dé un error de referencia indefinido.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top