如何通过需要自由函数的成员函数?

Jor*_*tao 106 c++ arguments function-pointers parameter-passing pointer-to-member

问题如下:考虑这段代码:

#include <iostream>


class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a();

    function1(&test);
    function1(&aClass::aTest); // <-- How should I point to a's aClass::test function?

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我如何使用a's aClass::test作为参数function1?我坚持这样做.

我想访问该班级的成员.

Die*_*ühl 128

使用函数指针没有任何问题.但是,指向非静态成员函数的指针与普通函数指针不同:需要在对象上调用成员函数,该对象作为函数的隐式参数传递.因此,上面的成员函数的签名是

void (aClass::*)(int, int)
Run Code Online (Sandbox Code Playgroud)

而不是你尝试使用的类型

void (*)(int, int)
Run Code Online (Sandbox Code Playgroud)

一种方法可以包括制作成员函数,static在这种情况下,它不需要调用任何对象,您可以将其与类型一起使用void (*)(int, int).

如果您需要访问类的任何非静态成员 你需要坚持使用函数指针,例如,因为函数是C接口的一部分,你最好的选择是始终传递void*给你的函数以函数指针和呼叫您的成员通过转发函数从中获取对象void*然后调用成员函数.

在适当的C++接口中,您可能希望查看函数对函数对象使用模板化参数来使用任意类类型.如果不希望使用模板化接口,则应使用以下内容std::function<void(int, int)>:您可以为这些创建适当可调用的函数对象,例如,使用std::bind().

使用类型或适当类型的模板参数的类型安全方法std::function<...>优于使用void*接口,因为它们消除了由于转换为错误类型而导致错误的可能性.

为了阐明如何使用函数指针来调用成员函数,这里有一个例子:

// the function using the function pointers:
void somefunction(void (*fptr)(void*, int, int), void* context) {
    fptr(context, 17, 42);
}

void non_member(void*, int i0, int i1) {
    std::cout << "I don't need any context! i0=" << i0 << " i1=" << i1 << "\n";
}

struct foo {
    void member(int i0, int i1) {
        std::cout << "member function: this=" << this << " i0=" << i0 << " i1=" << i1 << "\n";
    }
};

void forwarder(void* context, int i0, int i1) {
    static_cast<foo*>(context)->member(i0, i1);
}

int main() {
    somefunction(&non_member, 0);
    foo object;
    somefunction(&forwarder, &object);
}
Run Code Online (Sandbox Code Playgroud)

  • 我不喜欢这个答案,因为它使用“void*”,这意味着您可能会遇到非常讨厌的错误,因为它不再进行类型检查。 (3认同)

Mat*_*ips 73

@Pete Becker的答案很好,但您也可以在不将class实例作为function1C++ 11中的显式参数传递的情况下执行此操作:

#include <functional>
using namespace std::placeholders;

void function1(std::function<void(int, int)> fun)
{
    fun(1, 1);
}

int main (int argc, const char * argv[])
{
   ...

   aClass a;
   auto fp = std::bind(&aClass::test, a, _1, _2);
   function1(fp);

   return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 您需要为函数参数赋予变量名称,然后变量名称就是您实际传递的名称.所以:`void function1(std :: function <void(int,int)> functionToCall)`然后``functionToCall(1,1);`.我试图编辑答案,但有人拒绝了它,因为某些原因没有任何意义.我们会看看它是否会在某个时候被投票. (2认同)
  • 我发现此[post](http://stackoverflow.com/questions/14677997/stdfunction-vs-template)表示std :: function有严重的性能损失。 (2认同)
  • @kevin,您可能需要修改您的评论,因为该帖子的答案显示了基准测试中的缺陷。 (2认同)

Pet*_*ker 45

指向成员函数的指针与指向函数的指针不同.为了通过指针使用成员函数,您需要一个指向它的指针(显然)和一个要应用它的对象.所以适当的版本function1将是

void function1(void (aClass::*function)(int, int), aClass& a) {
    (a.*function)(1, 1);
}
Run Code Online (Sandbox Code Playgroud)

并称之为:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);
Run Code Online (Sandbox Code Playgroud)


Nic*_*oni 7

我问了一个类似的问题(C++ openframeworks moving void from other classes),但我发现的答案更清晰,所以这里是对未来记录的解释:

\n\n

it\xe2\x80\x99s 更容易使用 std::function ,如下所示:

\n\n
 void draw(int grid, std::function<void()> element)\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后调用为:

\n\n
 grid.draw(12, std::bind(&BarrettaClass::draw, a, std::placeholders::_1));\n
Run Code Online (Sandbox Code Playgroud)\n\n

甚至更简单:

\n\n
  grid.draw(12, [&]{a.draw()});\n
Run Code Online (Sandbox Code Playgroud)\n\n

在其中创建一个 lambda 调用通过引用捕获它的对象

\n

  • 此示例中不应使用占位符,因为该函数不接受任何参数。 (2认同)

Asa*_*han 7

需要注意的是,除非您可以更改采用该函数的代码的签名,否则没有(简单)的方法可以做到这一点。这将尝试用一种不具有与函数相同的闭包的语言来实现闭包(C++ 中闭包的签名不同)。

有两种实际方法可以实现此目的:

  1. 使用某种存储闭包的单例/全局变量,然后传递一个使用该闭包调用所需函数的辅助函数。这是一个例子:
#include <stdio.h>

template<class C, typename ReturnType, typename... Args>
class ClosureSingleton {
    typedef ReturnType (C::*FuncType)(Args...);

   public:
    static ClosureSingleton& getInstance() {
        static ClosureSingleton instance;
        return instance;
    }

    void setClosure(C* obj, FuncType f) {
        this->obj      = obj;
        this->function = f;
    }
    static ReturnType funcPtr(Args... args) {
        C* obj    = getInstance().obj;
        auto func = getInstance().function;

        return (obj->*func)(args...);
    }

   private:
    ClosureSingleton() {}

    C* obj;
    FuncType function;

   public:
    ClosureSingleton(ClosureSingleton const&) = delete;
    void operator=(ClosureSingleton const&)   = delete;
};

class aClass {
   public:
    void aTest1(int a, int b) { printf("%d + %d = %d\n", a, b, a + b); }
    int aTest2(int a, int b) { return a + b; }
};

void function1(void (*function)(int, int)) {
    function(1, 1);
}
int function2(int (*function)(int, int)) {
    return function(1, 1);
}

int main() {
    aClass tmp;
    ClosureSingleton<aClass, void, int, int>::getInstance().setClosure(
        &tmp, &aClass::aTest1);
    function1(&ClosureSingleton<aClass, void, int, int>::funcPtr);

    ClosureSingleton<aClass, int, int, int>::getInstance().setClosure(
        &tmp, &aClass::aTest2);
    printf(
        "function2: %d\n",
        function2(&ClosureSingleton<aClass, int, int, int>::funcPtr));

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

当然,这有一个明显的缺点,即每次调用之前都需要设置闭包,以及一些线程安全问题。不理想,但在特定情况下可能可行

  1. 使用 asmjit 或动态编译之类的工具来动态编译函数并将其传递给 C 代码。这仅适用于允许堆部分标记为可执行文件的机器。它也是非常不可移植的,因为您将编写汇编代码来完成此任务。然而,如果你让它工作,你确实会得到一个真正的闭包,尽管与大多数编程语言实现闭包的方式相比,创建闭包的成本要高得多(它们不重复函数汇编,而是使用上下文对象)
  2. 修补具有函数处理程序的 lib/dll,以更改其签名以允许上下文对象。这又是一个非常脆弱且非最优的解决方案。

我原来的答案并没有真正回答问题,但人们发现它很有用:

不知道为什么这个极其简单的解决方案被放弃了:

#include <stdio.h>

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d\n", a, b, a + b);
    }
};

template<class C>
void function1(void (C::*function)(int, int), C& c)
{
    (c.*function)(1, 1);
}
void function1(void (*function)(int, int)) {
  function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d\n", a , b , a - b);
}

int main (int argc, const char* argv[])
{
    aClass a;

    function1(&test);
    function1<aClass>(&aClass::aTest, a);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

1 - 1 = 0
1 + 1 = 2
Run Code Online (Sandbox Code Playgroud)


Lig*_*ica 6

从2011年开始,如果您可以更改function1,请按照以下步骤进行操作:

#include <functional>
#include <cstdio>

using namespace std;

class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

template <typename Callable>
void function1(Callable f)
{
    f(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main()
{
    aClass obj;

    // Free function
    function1(&test);

    // Bound member function
    using namespace std::placeholders;
    function1(std::bind(&aClass::aTest, obj, _1, _2));

    // Lambda
    function1([&](int a, int b) {
        obj.aTest(a, b);
    });
}
Run Code Online (Sandbox Code Playgroud)

现场演示

还要注意,我修复了损坏的对象定义(aClass a();声明了一个函数)。