2012-04-10 117 views
1

我正在爲C++開發一個用於數據結構類的模板化二叉搜索樹。到現在爲止,一切進展順利。這個問題涉及一些我不熟悉的基本C++東西,我需要幫助。C++函數指針轉換

我以前定義了遍歷樹並以不同順序訪問每個節點的函數。這裏討論的問題定義如下。

TreeNode.h

public: 
    static void PostOrderVisit(TreeNode<T>* node, void visit(const T& v)); 

TreeNode.cpp

template <class T> void TreeNode<T>::PostOrderVisit(TreeNode* node, void visit(const T& v)) { 
    if (node->leftChild != NULL) 
    PostOrderVisit(node->leftChild, visit); 
    if (node->rightChild != NULL) 
    PostOrderVisit(node->rightChild, visit); 
    visit(node->value); 
} 

這工作正常的測試程序,使節點和靜態調用PostOrderVisit。在一個朋友類(BinSTree.h/cpp)中,我實現了刪除樹中每個節點的方法,所以我認爲使用這個訪問者並調用我的Delete()函數是一個好主意每個節點(Delete()函數在BinSTree的測試程序中也能正常工作)。

該功能定義如下。

template <class T> void BinSTree<T>::ClearTree() { 
    TreeNode<T>::PostOrderVisit(this->root(), &BinSTree<T>::Delete); 
} 

這裏存在問題。 G ++說...

BinSTree.cpp:156: error: no matching function for call to ‘TreeNode<int>::PostOrderVisit(TreeNode<int>*, void (BinSTree<int>::*)(const int&))’ 
TreeNode.cpp:56: note: candidates are: static void TreeNode<T>::PostOrderVisit(TreeNode<T>*, void (*)(const T&)) [with T = int] 

在這種情況下,我認爲void (BinSTree<T>::*)(const T&)將是void (*)(const T&)的實例,但事實並非如此。我可以調用的唯一辦法由函數定義的認可是由鑄造函數指針這樣的:

TreeNode<T>::PostOrderVisit(this->root(), (void (*)(const T& v)) &BinSTree<T>::Delete); 

此識別功能,並適當地調用它,但是(這花了一些顯著的研究... ),C++成員函數有一個隱式參數,允許從內部訪問'this'關鍵字。將一個成員函數指針投射到一個普通函數指針上會完全拋棄'this'引用,從而導致我的Delete()方法分段錯誤(它使用'this'相當多)。

這一直是一個麻煩的地獄,我花了很多時間在這個項目的這麼一點點。任何人都可以向我展示一種方法:A:讓該功能在沒有投射的情況下被識別,或者B:如何在整個演員中保持這個「這個」參考。 ClearTree()和Delete()方法都在同一個類中。

在此先感謝。

+2

如果您已經在使用模板,請跳過函數指針並直接進入函子。可以內聯非虛擬的非函數指針調用。讓'visit'可以重載'operator()()'的模板。 – asveikau 2012-04-10 07:09:13

回答

1

非靜態方法爲「this」帶來一個隱式參數。例如。對於C :: f(int i)方法,你可以把它想象成f(C * this,int i)。你所做的任何演員,並擰緊這個簽名,你可能會發生不愉快的事情。您已經遇到崩潰,但更多險惡的文物可能會使程序在其他看似隨意的地方出現故障或崩潰。在的.cpp在.H

template <class C> 
static void PostOrderVisit(C* node, void (C::* visit)(const T& v)); 

(實際上如果它擁有所有成爲h的模板,否則鏈接錯誤):

您可以使用成員函數指針像這樣你要麼傳遞指向你的派生類(C在這裏)或指向基類(TreeNode,如原來的)的指針。在某些時候,您可能需要投射。

當您作爲訪問者傳遞正常功能時,您也可以保留原始功能。函數超載會很小心。

更通用的方式可以是使用std :: function。雖然它可能會有一些小的性能影響,但它會是最通用的。

例如(未編譯可能有一些小的語法錯誤):

static void PostOrderVisit(TreeNode<T>* node, std::function<void (const T& v)> visit); 

裏面PostOrderVisit你只是做訪問(值),如像普通功能一樣調用。

當您調用PostOrderVisit時,您可以使用std :: bind或boost :: bind的所有功能來隨意攜帶更多的額外信息。例如。

PostOrderVisit(this-> root(),std :: bind(& BinSTree :: Delete,this));

3

首先,PostOrderVisit應該將函數參數作爲指針,即PostOrderVisit(TreeNode<T>* node, void (*visit)(const T& v))

但是,這不會解決你的問題,因爲你傳遞給它一個非靜態的成員函數。您傳遞給它的函數必須是類中的static,或者您可以使用類似std::function而不是函數指針參數,即PostOrderVisit(TreeNode<T>* node, std::function<void(const T&)> visit)

編輯 在這種情況下,我認爲你有兩種方法可以做到這一點:一個是改變設計以適應參數,這意味着你不能使用成員函數作爲參數。其次是更改代碼以適應您的設計,並向老師解釋由於其限制而必須更改界面,並解釋這些限制。

使用普通函數指針作爲參數的問題是成員函數對類的實例有隱式和隱藏參數this。普通函數沒有這個隱藏參數,所以編譯器禁止你使用成員函數。解決方法是使用正常的函數,這不是非常C++ - ish,另一種是使用static成員函數(因爲它們沒有this指針),或者使用類似std::function的東西。

至於如何使用std::function,你可以像我所示的那樣在PostOrderVisit的聲明和定義中使用它。當你打電話給你時,你可以這樣做:

template <class T> void BinSTree<T>::ClearTree() { 
    TreeNode<T>::PostOrderVisit(this->root(), std::mem_fn(&BinSTree<T>::Delete)); 
} 
+0

不知道多少leadway我必須改變參數PostOrderVisit需要,因爲它是一個學校項目(一個非常可怕的概述)。我可以在函數被調用的地方使用std :: function嗎?還是必須在PostOrderVisit的聲明中?對不起,我對C++還是有點新鮮的。 – 2012-04-10 07:14:37

+0

@JayElrod爲我的回答添加了一些解釋。 – 2012-04-10 07:28:50