2012-04-10 5 views
1

Я разрабатываю шаблонное дерево двоичного поиска в C++ для класса структур данных прямо сейчас. До сих пор все шло хорошо. Эта проблема связана с некоторыми nitty gritty C++, с которыми мне не знакомы, и мне нужна помощь.C++ function pointer casting

Я ранее определял функции, которые пересекают дерево и посещают каждый узел в разных порядках. Здесь речь идет о следующем.

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.

В классе friend (BinSTree.h/cpp) я реализую метод, который удаляет каждый узел в дереве, поэтому я подумал, что было бы неплохо использовать этого посетителя и вызвать функцию Delete() каждый узел (функция Delete() также отлично работает в тестовых программах для BinSTree).

Эта функция определяется следующим образом.

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

И здесь проблема. г ++ говорит ...

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++ имеют неявный параметр, который позволяет получить доступ к этому ключевому слову изнутри. Приведение указателя функции-члена к указателю простой функции полностью сбрасывает эту ссылку, заставляя метод Delete() выполнить seg fault (он использует это «совсем немного»).

Это был ХАЛ от хлопот, и я потратил немало времени на такую ​​небольшую часть этого проекта. Может ли кто-нибудь показать мне способ либо A: сделать функцию признанной без кастинга, либо B: как поддерживать «эту» ссылку во время трансляции. Методы ClearTree() и Delete() находятся в одном классе.

Заранее спасибо.

+2

Если вы уже используете шаблоны, пропустите указатели на функции и перейдите непосредственно к функторам. Не виртуальные вызовы с неинтерфейсом могут быть встроены. Сделайте 'посещение' шаблон, который может перегрузить' operator()() '. – asveikau

ответ

1

Нестатические методы принимают неявный параметр для «this». Например. для метода C :: f (int i) вы можете думать о нем как f (C * this, int i). Любое литье, которое вы делаете, и закручивает эту подпись, вы можете ожидать неудачи. Вы уже столкнулись с авариями, но более зловещие артефакты могут сделать программу неправильной или неудачной в других, казалось бы, случайных местах.

Вы можете использовать указатель на функцию-член, как это:

в .h

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

в .cpp (на самом деле, если это шаблон имеет все, чтобы быть в час, в противном случае ошибки связи)

template <class T> 
template <class C> 
void TreeNode<T>::PostOrderVisit(C* node, void (C::* visit)(const T& v)) 
{ 
    // ... 
    T value; 
    (node->*visit)(value); 
    // ... 
} 

Вы либо передать указатель на свой производный класс (C, как здесь) или указатель на базовый класс (TreeNode как в оригинале). В какой-то момент вам может понадобиться бросить.

Вы также можете оставить оригинальную функцию, когда вы проходите обычную функцию в качестве посетителя. Функция перегрузки будет заботиться.

Более общим способом может быть использование функции std ::. Хотя это может иметь незначительную производительность, это было бы наиболее общим.

например. (Не компилируется может иметь некоторые незначительные ошибки синтаксиса):

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 :: Удалить, это));

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

Не уверен, сколько строк мне нужно изменить, если PostOrderVisit меняет параметры, поскольку это школьный проект (очень ужасно очерченный). Могу ли я использовать std :: function, где вызывается функция? Или это должно быть в объявлении PostOrderVisit? К сожалению, я все еще немного знаком с C++. –

+0

@JayElrod Добавил некоторые пояснения к моему ответу. –