【问题标题】:How can I pass a class method to another function like what happen in thread constructor如何将类方法传递给另一个函数,就像线程构造函数中发生的那样
【发布时间】:2014-05-19 12:30:49
【问题描述】:

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

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)...)));
  }

【问题讨论】:

  • 它是构造函数,而不是承包商或构造函数。而使用普通的 func 指针,你想要的是不可能的。
  • 好吧,这只是一个拼写错误!
  • @HRH '好吧,这只是一个拼写错误!' 多次是的。正确的措辞和正确的英语很重要。请编辑您的问题,进行更正。
  • executeQuery怎么可能是class x的构造函数?
  • 不是class x的构造函数,你问这个干嘛?我只是以线程构造函数为例来说明我的相关解决方案存在于哪里!

标签: c++ function-pointers argument-passing


【解决方案1】:

更新

在您的示例中,您使用带有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 包装类成员函数(参见here):

#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, ... 是您稍后将在使用函数指针时提供的参数的占位符。

【讨论】:

  • _1、_2、_3、_4 是不必要的,不是吗?
  • @HRH:我认为您的实际问题出在某个地方并更新了我的答案。
  • 感谢您的回复,但您的回答对我不起作用,并且出现编译错误!无论如何....
【解决方案2】:

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

functor 是所有可以用() 操作符调用的东西。所以,函数指针就是函子:

void (*pf)();

// we can do it
pf();

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

struct Functor
{
    void operator ()();
};
Funtor f;

// we also can do it
f();

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

例如:

#include <iostream>
#include <functional> // for std::bind

template <typename F>
void call_functor(F f)
{
    f();
}

void foo() { std::cout << "foo\n"; }
struct bar { void operator ()() { std::cout << "bar\n"; } };

void WithParam(int i) { std::cout << "param : " << i << "\n"; }

class Cls
{
public:
    void member() { std::cout << "Cls::member\n"; }
};

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()调用,因为它有一个参数,但我们可以通过将WithParam的参数绑定123来使用WithParam

std::bind不仅可以绑定参数,还可以绑定this指针和成员函数指针。所以我们也可以这样做:call_functor(std::bind(&amp;Cls::member, &amp;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&lt;&gt;。它是函子的多态包装器。它比函子慢一点,但如果你介意使用模板,它可能是一个很好的解决方案。

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

用法几乎等于函子。 (也许你需要显式地转换成std::function&lt;&gt;)事实上,std::function 也是函子! (可以用()操作符调用..)


编辑: 嗯.. 我刚刚注意到您正在像这样使用f

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

sqlite3_exec 的第三个参数可能是函数指针吧?它会变得复杂..

根据heresqlite3_exec 的第四个参数将作为第一个参数传递给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);
}

edit2:嗯,我明白了问题所在。一方面,如果你这样做,效果会很好:

    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占位符。 (如果你想了解占位符,请搜索 google..)

在这种情况下,我认为不需要占位符,但是如果没有占位符,则会发生错误...


edit3:我们应该使用占位符的原因:Why are placeholders required in std::bind in this case?

【讨论】:

  • 还有一个可调用对象:lamda 表达式
  • 对不起,它不起作用!我尝试使用您的解决方案,结果如下: 1. 您的 typedef 不起作用,我不得不将其更改为:typedef std::function&lt;int (void*, int, char**, char**)&gt; ExecQueryFunctor; 2. 编写代码后,我的编译器会这样说:error: no matching function for call to ‘x::executeQuery(std::string&amp;, std::_Bind_helper&lt;false, int (myclass::*)(void*, int, char**, char**), mycalss* const&gt;::type)
  • @texasbruce 我试图尽可能简单地解释,所以我没有提到 lambda...
【解决方案3】:

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

【讨论】:

    猜你喜欢
    • 2012-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-18
    • 1970-01-01
    相关资源
    最近更新 更多