C++函数指针强制转换

C++ function pointer casting

本文关键字:转换 指针 函数 C++      更新时间:2023-10-16

我现在正在为数据结构类开发一个模板化的二叉搜索树,C++。到目前为止,一切都很顺利。这个问题涉及一些我不熟悉的C++细节,我需要帮助。

我之前已经定义了遍历树并以不同顺序访问每个节点的函数。这里所讨论的那个定义如下。

TreeNode.h

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

树节点.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:如何在整个转换中保持"this"引用。ClearTree() 和 Delete() 方法都在同一个类中。

提前谢谢。

首先,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++,另一种是使用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));
}

非静态方法采用"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 中(实际上如果它是一个模板,它必须全部在 H 中,否则链接错误)

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::function。虽然它可能有一些轻微的性能影响,但它是最通用的。

例如(未编译可能有一些小的语法错误):

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

在PostOrderVisit中,您只需执行访问(值),例如像正常功能一样调用。

当您调用PostOrderVisit时,您可以使用std::bind或boost::bound的所有功能来携带尽可能多的额外信息。 例如

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