2009-11-27 36 views
5

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:Operador de sobrecarga << para una clase con plantilla

Estos dos métodos se encuentran 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 sobrecarga el operador "< <" de la clase BinaryTree a utilizar: cout < < árbol y ofstream f < < árbol, pero recibo el siguiente mensaje de error: undefined reference to `operador < < (std :: basic_ostream> &, & BinaryTree)'

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 explica todo lo necesario porque pierdo todo mi tiempo en errores como este.

EDITAR: árbol en saveToFile() se declara: BinaryTree < Word> tree.

+0

¿A qué tipo de árbol se hace referencia en saveToFile? –

+0

¿Usó espacios de nombres en su código (particularmente al declarar 'BinaryTree ' y su sobrecarga de 'operator <<')? Si es así, muéstrelos en contexto. –

+0

Por favor, publique el texto exacto del mensaje de error dado por el compilador, en su totalidad. –

Respuesta

8

El problema es que el compilador no está intentando utilizar la plantilla operator<< 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 gratuita que toma un argumento non_template_test por la constante referencia:

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

Lo mismo sucede con clases de plantilla, aunque en este caso es un poco menos intuitiva . Cuando declaras (y no defines) una función amiga dentro del cuerpo de la clase de la plantilla, estás declarando una función libre con esos argumentos exactos.Tenga en cuenta que se está declarando una función y 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 se declaran libres pero no definidos. La parte difícil aquí es que esas funciones gratuitas no son una plantilla, sino que se declaran funciones gratuitas regulares. Cuando se agrega la función de plantilla en la mezcla que se 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 realiza la función principal que crea la instancia de la plantilla template_test con el tipo int y que declara la función libre void f(template_test<int> const &) que no se templated. Cuando encuentra la llamada f(t1), hay dos símbolos f que coinciden: la no plantilla f(template_test<int> const &) declarada (y no definida) cuando se instanciaron template_test y la versión de plantilla que se declara y define en 1. La versión no templada tiene prioridad y el compilador la compara.

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 con plantilla como un amigo. Son sutilmente diferentes, pero en la mayoría de los casos son equivalentes.

Tener el compilador generar las funciones no moldeados por nosotros:

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

Esto tiene el efecto de crear tantas funciones gratuitas no moldeados según 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 alcance adjunto.

crear una versión en plantilla un amigo

Para hacer la plantilla de un amigo hay que tenerlo ya declarada y decirle al compilador que el amigo que queremos es en realidad una plantilla y no una función gratuita no moldeado:

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 hay que remitir declarar la plantilla. Para declarar la plantilla f primero debemos reenviar declarar la plantilla test. La declaración de amigo se modifica para incluir los corchetes angulares que identifican que el elemento que estamos haciendo un amigo es en realidad una plantilla y no una función gratuita.

Volver al problema

Volviendo a su ejemplo particular, la solución más sencilla es tener el compilador genera las funciones para usted por inlining la declaración de la función 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 operator<< sin plantilla para cada tipo de instancia, y esa función generada delega en el método dump de la plantilla.

+0

Creo que este me ha enseñado mucho. ¡Gracias! – peterJk

+0

+1 Siempre quise tener esta información en un lugar útil. –

+0

+1 Resolvió mi problema :). – Yuri

2

Asegúrese de que las definiciones de plantilla completas (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 independiente no funcionan juntas.

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

+1

Irrelevante para el problema: si bien es un buen consejo general, no aborda el problema de por qué el enlazador no encuentra la función. –

+1

De hecho, eso daría un error de enlazador, pero no con el código en la pregunta. En el código de la pregunta, la función que no se encuentra no es una plantilla, sino una función libre sin plantillas. Tenga en cuenta que el 'operado' 'templated' 'no se declara adelante, apenas definido. Esto significa que el compilador lo está usando y el símbolo está definido para que el enlazador lo use, o como sucede, el compilador ni siquiera intenta hacer coincidir el 'operador <<' con plantilla y donde se define la plantilla es irrelevante para el problema. –

3

usted no necesita la declaración operador de plantilla y usted tiene que declarar el operador "amigo" para su clase para tener acceso concedido a otras clases, en este caso std :: cout

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

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

+0

@Charles: Tiene razón, pero parece que el árbol binario solo almacena objetos Word. –

+0

Haga caso omiso de mi comentario anterior ... no necesitaría el parámetro de plantilla si el operador '<<' está definido en línea como una función amiga. –

+0

+1 incluso si no explica por qué, o el hecho de que podría ser más explícito en cuanto a que la definición debe residir dentro de la definición de la plantilla BinaryTree. Pero después de todo, proporciona una solución. –

3

Cuando la sobrecarga del 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; 
} 
+2

Irrelevante para el problema: esto no tiene nada que ver con el hecho de que el vinculador no encontrará la definición de la función. –

Cuestiones relacionadas