首页 > 解决方案 > 将函数和成员函数都传递给模板方法

问题描述

我一直在研究这个问题一段时间,我希望在这里得到一些澄清。

我开发了一个模板类。它的一种方法输入两个具有不同参数的函数,如下所示:

template<typename S, typename T, typename R>
void TGraph<S,T,R>::BFS(const S &key, bool(*relValid)(R), void(*manip)(string, string, R)) {
    //Being here R,S members of the template class TGraph, do stuff including
    if(relValid("some R") ){
    manip("some S", "another S", "someR");
    }

}

在 main.cpp 中传递标准函数时,这就像一个魅力。

int main(){
    TGraph2<string, User, Relation> graph;
    graph.BFS("Rube", alwaysValid, printRelation);
}
void printRelation(string id1, string id2, Relation rel){
    cout<<id1<<" ha una relazione con "<<id2<<" di tipo "<<rel.type<<endl;
}
bool alwaysValid(Relation rel){
    return true;
}

在另一个类中使用模板类并改用成员函数时,我无法编译。

class DataSet {
private:
    TGraph<string, User, Relation> _users;
....
}

我发现这是因为指针到函数和指针到成员函数类型之间的差异。一个解决方案是使成员成为静态成员,这在我的情况下是不可能的(我需要访问一些非静态成员)。``

现在我想知道,是否存在一些“干净”的解决方案来允许 BFS 方法与任何函数(成员或非成员)一起工作?或者至少让它与成员函数一起工作(这实际上是我的需要)。

标签: c++templates

解决方案


成员函数和非成员函数的调用方式完全不同。结果,它们不能很好地混合。解决这个问题的方法是接受函数对象而不是函数指针或指向成员函数的指针是接受函数对象。这些仍然不一定会立即接受成员函数,但仔细实现结果函数可以透明地处理各种函数对象。

要处理函数对象,大致有两种不同的方法:

  1. 接受函数对象作为模板参数。这样做对性能很有好处:如果编译器可以看穿调用链,编译器会很高兴地内联所有操作(并且传递 lambda 函数而不是函数指针使得这相当成功)。这种方法的缺点是需要公开一个函数模板。取决于可能相当有问题的上下文。此外,所需的签名在函数签名中不可见,需要在其他地方指定。

    你可以写你的BFS()成员如下。使用std::invoke()处理传递任何可以被认为是函数对象的东西,包括成员函数指针和成员指针:

    template<typename S, typename T, typename R, typename RelValid, typename Manip>
    void TGraph<S,T,R>::BFS(const S &key, RelValid relValid, Manip manip) {
        //Being here R,S members of the template class TGraph, do stuff including
        if(std::invoke(relValid, "some R") ){
            std::invoke(manip, "some S", "another S", "someR");
        }
    }
    
  2. 接受仅指定函数接口的类型擦除函数对象。std::function<RC(T...)>显然是类型擦除函数对象的首选。函数指针、成员函数指针和函数对象通常很容易转换为兼容std::function<RC(T...)>类型。

    使用std::function<...>替换函数指针很简单:函数指针已经使用了作为模板参数所需的签名std::function<...>

    template<typename S, typename T, typename R>
    void TGraph<S,T,R>::BFS(const S &key, std::function<bool(R)> relValid, std::function<void(string, string, R)> manip) {
        //Being here R,S members of the template class TGraph, do stuff including
        if(relValid("some R") ){
            manip("some S", "another S", "someR");
        }
    }
    

选择哪种方法在一定程度上取决于预期用途。如果函数对象被大量使用,例如,它为访问的每个节点调用一次,我更喜欢使用模板参数。该函数只使用了几次,或者它需要显示在我将使用std::function<...>(或类似)的非模板化界面中。


推荐阅读