重载运算符<<对于模板类

时间:2009-11-27 21:57:33

标签: c++ templates operator-overloading

我正在尝试为返回流的二叉树实现一个方法。我想使用方法中返回的流来显示屏幕中的树或将树保存在文件中:

这两个方法在二叉树的类中:

声明:

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

此方法位于UsingTree类:

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

所以我重载了运算符“&lt;&lt;”要使用的BinaryTree类:cout&lt;&lt;树和流f&lt;&lt;树,但我收到下一条错误消息:未定义引用`operator&lt;&lt;(std :: basic_ostream&gt;&amp;,BinaryTree&amp;)'

P.S。树存储Word对象(带有int的字符串)。

我希望你能理解我的英语不好。谢谢! 我想知道一个关于STL的初学者的好文本,它解释了所有必要的,因为我把所有时间浪费在这样的错误上。

编辑:saveToFile()中的树被声明:BinaryTree&lt; Word&gt;树。

4 个答案:

答案 0 :(得分:8)

问题是编译器没有尝试使用您提供的模板operator<<,而是使用非模板版本。

当您在类中声明一个朋友时,您将在封闭范围内注入该函数的声明。以下代码具有声明(而不是定义)通过常量引用获取non_template_test参数的自由函数的效果:

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

模板类也是如此,即使在这种情况下它也不那么直观。当您在模板类主体中声明(而不是定义)友元函数时,您将声明具有该确切参数的自由函数。请注意,您要声明一个函数,而不是模板函数:

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

声明了这些自由函数但未定义。这里棘手的部分是那些自由函数不是模板,而是声明了常规自由函数。将模板函数添加到混合中时,您会得到:

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

当编译器命中main函数时,它会实例化template_test类型为int的模板,并声明未模板化的自由函数void f( template_test<int> const & )。当它找到调用f( t1 )时,有两个匹配的f符号:实例化f( template_test<int> const & )时声明(未定义)的非模板template_test和模板版本在1声明和定义。非模板化版本优先,编译器匹配它。

当链接器尝试解析f的非模板化版本时,它找不到符号,因此失败。

我们能做什么?有两种不同的解决方案。在第一种情况下,我们使编译器为每个实例化类型提供非模板化函数。在第二种情况下,我们将模板化版本声明为朋友。它们略有不同,但在大多数情况下都是等价的。

让编译器为我们生成非模板化函数:

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

这具有根据需要创建尽可能多的非模板化自由函数的效果。当编译器在模板test中找到友元声明时,它不仅会找到声明,还会找到实现,并将两者都添加到封闭范围。

将模板版本设为朋友

要使模板成为朋友,我们必须已经声明它并告诉编译器我们想要的朋友实际上是模板而不是非模板化的自由函数:

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

在这种情况下,在将f声明为模板之前,我们必须转发声明模板。要声明f模板,我们必须先转发声明test模板。朋友声明被修改为包括尖括号,用于标识我们正在制作朋友的元素实际上是模板而不是自由函数。

回到问题

回到你的特定例子,最简单的解决方案是让编译器通过内联friend函数的声明为你生成函数:

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

使用该代码,您强制编译器为每个实例化类型生成非模板operator<<,并且生成的函数委托模板的dump方法。

答案 1 :(得分:3)

您不需要模板运算符声明,您必须声明您的类的运算符“friend”已授予对其他类的访问权限,在本例中为std :: cout

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

推荐阅读:http://www.parashift.com/c++-faq-lite/friends.html

答案 2 :(得分:3)

当重载<<运算符时,要使用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;
}

答案 3 :(得分:2)

确保完整的模板定义(而不仅仅是原型)在include(即.h,.hpp)文件中作为模板,并且单独的编译不能一起工作。

我不知道@Dribeas正在使用什么链接器,但这肯定会导致GNU链接器给出未定义的引用错误。