如何将类方法传递给另一个函数,就像线程构造函数中发生的那样

How can I pass a class method to another function like what happen in thread constructor

本文关键字:构造函数 线程 类方法 函数 另一个      更新时间:2023-10-16

我想将一个类方法传递给另一个函数,我写了这些代码:

class x {
   executeQuery(std::string query, int (*f)(void* cpuInfo, int argc, char** argv, char** azColName))
   {
          int rc = sqlite3_exec(db, query.c_str(), &f, 0, &errMessage);
          ...
   }
};

上面的代码显示了我从类构造函数调用的函数!

myclass()
{
    xObject->executeQuery("test", &(myclass::myfunction));
}

这部分代码展示了我如何将 myfunction 传递给该方法!但是,在编译期间,我收到此错误:

error: ISO C++ forbids taking the address of an unqualified or parenthesized non-static member function to form a pointer to member function.

我用相同的语法调用了线程构造函数!但听起来线程构造函数制作了一个我无法理解的新函数指针!您知道我如何在有/没有螺纹解决方案的情况下解决此问题吗?下面的代码显示了线程构造函数标头:

  template<typename _Callable, typename... _Args>
  explicit 
  thread(_Callable&& __f, _Args&&... __args)
  {
    _M_start_thread(_M_make_routine(std::__bind_simple(
            std::forward<_Callable>(__f),
            std::forward<_Args>(__args)...)));
  }

更新

在您的示例中,您将函数指针与 sqlite3_exec 一起使用。 sqlite3_exec需要一个 C 样式函数作为参数callback。您不能在此处使用指向类成员函数的指针!

这样的事情可能是一种解决方法。但要小心线程安全:

namespace wrap {
    typedef std::function<int(void*,int,char**,char**)> QueryFunction;
    inline QueryFunction& function() {
        static QueryFunction f;
        return f;
    }
    void callback(void* cpuInfo, int argc, char** argv, char** azColName);
}
void wrap::callback(void* cpuInfo, int argc, char** argv, char** azColName) {
     function()(cpuInfo, argc, argv, azColName);
}

class x {
   executeQuery(std::string query, QueryFunction f)
   {
        wrap::function() = f;
        int rc = sqlite3_exec(db, query.c_str(), &wrap::callback, 0, &errMessage);
        ...
   }
};
MyClass* obj = ...;
xObject->executeQuery("test", std::bind(myclass::myfunction, obj));

旧答案

您可以使用 std::function 包装类成员函数(请参阅此处):

#include <functional>
typedef std::function<int(void*,int,char**,char**)> QueryFunction;
class x {
public:
    void executeQuery(std::string query, QueryFunction f) {
        f(ptr,0,"hello","test"); // call function
    }
};
MyClass* obj = ...;
using std::placeholders;
xObject->executeQuery("test", std::bind(myclass::myfunction, obj, _1, _2, _3, _4));

为了提供指向类成员函数的指针,还需要提供一个对象,应在其上调用成员函数。

std::bind允许您执行此操作(甚至更多)。在上面的情况下,要std::bind的第一个参数是指向类成员函数的指针,第二个参数是用于调用函数的对象。以下论点_1 , ...是稍后使用函数指针时将提供的参数的占位符。

std::thread和许多其他库不接收函数指针。他们接收函子

函子是可以用运算符调用的所有东西()。因此,函数指针是函子:

void (*pf)();
// we can do it
pf();

我们也可以调用重载的对象 operator () .

struct Functor
{
    void operator ()();
};
Funtor f;
// we also can do it
f();

成员函数指针不是函子。(你不能mpf(this);. this->*mpf();是正确的。但是,std::thread的构造函数执行绑定

例如:

#include <iostream>
#include <functional> // for std::bind
template <typename F>
void call_functor(F f)
{
    f();
}
void foo() { std::cout << "foon"; }
struct bar { void operator ()() { std::cout << "barn"; } };
void WithParam(int i) { std::cout << "param : " << i << "n"; }
class Cls
{
public:
    void member() { std::cout << "Cls::membern"; }
};
int main()
{
    // we can do these, because both foo and b are functor.
    bar b;
    call_functor(foo);
    call_functor(b);
    // bind `123`
    call_functor(std::bind(WithParam, 123));
    // bind `c`
    Cls c;
    call_functor(std::bind(&Cls::member, &c /* this pointer */));
}

看看这个:call_functor(std::bind(WithParam, 123)); .尽管WithParam不能用f()调用,因为它有一个参数,但我们可以通过将其参数绑定123来使用WithParam

std::bind不仅可以执行绑定参数,还可以将此指针与成员函数指针绑定。所以我们也可以这样做:call_functor(std::bind(&Cls::member, &c /* this pointer */)); .


我们无法知道所有函子的类型。例如:void (*)()struct barstd::bind...

因此,要接收函子作为参数,我们应该使用模板。例如,查看std::thread的构造函数:

template<typename _Callable, typename... _Args>
explicit 
thread(_Callable&& __f, // <-- here
    ...

在您的情况下,您应该这样做:

class x
{
public:
    template <typename Functor>
    void executeQuery(std::string query, Functor f)
    {
        ...
        int result = f(cpuInfo, argc, argv, azColName);
        ...
    }
};
...
myclass()
{
    xObject->executeQuery("test", std::bind(&myclass::myfunction, this));
}

你介意使用模板(也许你想隐藏executeQuery的实现)吗?然后,还有另一种解决方案,std::function<> .它是函子的多态包装器。它比函子慢一点,但如果您介意使用模板,它可能是一个很好的解决方案。

class x
{
public:
    void executeQuery(std::string query,
        const std::function<int ()(void*, int, char**, char**)> &f);
};

用法几乎等于函子。(也许你需要显式转换为std::function<>)事实上,std::function也是函子!(可以使用()运算符调用它。


编辑:嗯。我刚刚注意到你正在使用这样的f

int rc = sqlite3_exec(db, query.c_str(), &f, 0, &errMessage);

也许sqlite3_exec的第三个参数是函数指针,对吧?它会变得复杂。

根据这里,sqlite3_exec的第 4 个参数将作为第 1 个参数传递到f。然后,我们可以编写这样的代码:

// header file of `class x`
class x
{
private:
    static void ExecuteQuery_Callback(void *param, int argc, char** argv, char** azColName);
    // use std::function<>
    typedef std::function<int (void*, int, char**, char**)> ExecQueryFunctor;
    void executeQuery_impl(std::string query, const ExecQueryFunctor &f);
public:
    // wrapper
    template <typename F> void executeQuery(std::string query, F f)
    {
        executeQuery_impl(query, ExecQueryFunctor(f));
    }
};
// .cpp file of `class x`
void x::executeQuery_impl(std::string query, const x::ExecQueryFunctor &f)
{
    int rc = sqlite3_exec(db, query.c_str(), ExecuteQuery_Callback, &f, &errMessage);
    ...
}
void x::ExecuteQuery_Callback(void *param, int argc, char** argv, char** azColName)
{
    const ExecQueryFunctor *pfunctor = (const ExecQueryFunctor *)param;
    (*pfunctor)(NULL, argc, argv, azColName);
}

编辑2:嗯,我看到了问题。首先,如果您这样做,它会很好地工作:

    xObject->executeQuery("test", std::bind(&myclass::myfunction, this));

更改为:

using namespace std::placeholders;
...
    xObject->executeQuery("test", std::bind(&myclass::myfunction, this, _1, _2, _3, _4));

_1_2_3_4占位符。(如果您想了解占位符,请搜索谷歌。

在这种情况下,我认为不需要占位符,

但是如果没有占位符,就会发生错误......


edit3:我们应该使用占位符的原因:为什么在这种情况下在 std::bind 中需要占位符?

经过多次闲逛并尝试可能的解决方案,我发现我的问题没有解决方案。我与一位专业的 c++ 程序员(Hedayat Vatankhah)进行了交谈,他解释说,由于类的方法在 c++ 中具有特殊地址,因此您不能将其指针传递给期望绝对地址的 c 函数(如 sqlite3_exec)。顺便说一句,听起来 gcc 有一个扩展可以将方法地址转换为绝对地址(但我离开了这个解决方案,我尝试一些简单的 sqlite3 函数来创建新版本的 sqlite3_exec)