2009-11-27 69 views
5

我想實現一個返回流的二叉樹的方法。我想用一個方法返回的流,以顯示該樹在屏幕或保存樹在一個文件中:重載運算符<<爲模板類

這兩種方法都在類的二叉樹:

聲明:

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

所以我重載二叉樹類使用的運營商 「< <」: COUT < <樹和ofstream的˚F< <樹中,但我接收下一個錯誤消息:未定義的參考`操作者< <(標準:: basic_ostream> &,二叉樹&)」

P.S.樹存儲Word對象(帶int的字符串)。

我希望你能理解我可憐的英語。謝謝! 我想知道一個關於STL的初學者的好文本,它解釋了所有必要的,因爲我浪費了我所有的時間在這樣的錯誤。

編輯:saveToFile()中的樹聲明爲:BinaryTree < Word> tree。

+0

saveToFile中引用了什麼類型的樹? – 2009-11-27 22:03:01

+0

您是否在代碼中使用了命名空間(特別是當聲明'BinaryTree '和'operator <<'的重載)時?如果是這樣,請在上下文中顯示它們。 – 2009-11-27 22:03:03

+0

請完整地發佈編譯器給出的錯誤消息的確切文本。 – 2009-11-27 22:46:53

回答

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

當編譯器擊中它實例化模板template_testint類型的主要功能,並且聲明免費功能void f(template_test<int> const &)是不模板。當它找到呼叫f(t1)時,有兩個f符號匹配:當template_test已實例化並且已聲明和定義1的模板版本時聲明(而未定義)的非模板f(template_test<int> const &)。非模板版本優先,編譯器與之匹配。

當鏈接器嘗試解析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模板。朋友聲明被修改爲包含尖括號,用於標識我們正在交友的元素實際上是模板而不是自由函數。

回到問題

再回到你的具體的例子,最簡單的解決方案是具有編譯器通過內聯友元函數聲明爲您生成功能:

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方法生成了函數委託。

+0

我認爲這個教會了我很多。謝謝! – peterJk 2009-11-30 20:54:01

+0

+1我一直想在一個方便的地方獲得這些信息。 – 2010-08-17 11:35:10

+0

+1解決了我的問題:)。 – Yuri 2011-06-15 07:46:56

2

確保完整的模板定義(而不僅僅是原型)作爲模板包含在(即.h,.hpp)文件中,而單獨的編譯不能一起工作。

我不知道@Dribeas正在使用什麼鏈接器,但這肯定會導致GNU鏈接器給出未定義的引用錯誤。

+1

與問題無關:儘管這是一個很好的一般性建議,但它沒有解決鏈接程序未能找到函數的問題。 – 2009-11-28 01:12:09

+1

事實上,這會給鏈接器錯誤,但不會與問題中的代碼。在問題代碼中,未找到的函數不是模板,而是非模板化的自由函數。請注意,模板化的'operator <<'不是前面定義的轉發聲明。這意味着編譯器正在使用它,並且符號被定義爲鏈接器使用,或者恰巧,編譯器甚至不會嘗試匹配模板化的'operator <<',並且模板被定義爲與問題無關。 – 2009-11-28 11:56:02

3

你不需要模板運算符聲明,你必須申報的經營者的「朋友」爲你的類有授權訪問其他類,在這種情況下的std ::法院

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

recomended閱讀:http://www.parashift.com/c++-faq-lite/friends.html

+0

@Charles:你是對的,但似乎二叉樹只存儲Word對象。 – 2009-11-27 22:46:29

+0

不理會我以前的評論......如果'<<'運算符被內聯定義爲朋友函數,則不需要模板參數。 – 2009-11-27 22:51:45

+0

+1,即使它沒有解釋爲什麼,或者由於定義必須位於BinaryTree模板定義中,它可能更明確。但畢竟它提供了一個解決方案。 – 2009-11-28 01:13:43

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

與問題無關:這與鏈接器找不到函數定義的事實無關。 – 2009-11-28 01:10:56