如何在期望有自由函数的地方传递成员函数?


122

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

#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;
}

我如何使用aaClass::test作为参数function1?我坚持这样做。

我想访问该班的成员。



16
这绝对不是重复的(至少不是链接的特定问题的重复)。这个问题是关于如何声明一个指向函数指针的成员的。这是关于如何将指针传递给非静态成员函数的参数。
CarLuva 2014年

Answers:


144

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

void (aClass::*)(int, int)

而不是您尝试使用的类型

void (*)(int, int)

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

如果您需要访问类的任何非静态成员, 并且需要坚持使用函数指针,例如,由于该函数是C接口的一部分,则最好的选择是始终将a传递给带有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);
}

好吧,我喜欢这个答案!能否请您指定“通过通过从void *获取对象然后调用成员函数的转发函数来调用成员”或共享指向它的有用链接的含义?谢谢
豪尔赫·

我想我明白了。(我已经编辑了您的帖子)感谢您的解释和示例,真的很有帮助。只需确认一下:对于我要指出的每个成员函数,我都必须创建一个转发器。对?
豪尔赫·雷涛

好吧,是的。根据您使用模板的效率,您可以创建可与不同类和成员函数一起使用的转发模板。我想,如何做到这一点将是一个单独的功能;-)
DietmarKühl2012年

1
我不喜欢这个答案,因为它使用了void*,这意味着您可能会收到非常讨厌的错误,因为它不再经过类型检查了。
Superlokkus

@Superlokkus:能否请您给我们一个启发?
DietmarKühl2015年

88

@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;
}

1
是对的void function1(std::function<void(int, int)>)
德庆

2
您需要给函数参数指定一个变量名,然后变量名才是您实际传递的。因此:void function1(std::function<void(int, int)> functionToCall)然后functionToCall(1,1);。我试图编辑答案,但是有人出于某种原因拒绝了它,因为它没有任何意义。我们将在某个时候看是否赞成它。
Dorky工程师

1
@DorkyEngineer这很奇怪,我认为您一定是对的,但我不知道这么长时间以来该错误是怎么被忽视的。无论如何,我现在已经编辑了答案。
马特·菲利普斯

2
我发现这篇文章说std :: function有严重的性能下降。
凯文(Kevin)2015年

2
@kevin,您可能需要修改您的评论,因为该帖子的答案表明基准测试存在缺陷。
豪尔赫·雷涛

54

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

void function1(void (aClass::*function)(int, int), aClass& a) {
    (a.*function)(1, 1);
}

并称之为:

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

1
非常感谢你。我刚刚发现括号在这里计数:function1(&(aClass :: test),a)适用于MSVC2013,但不适用于gcc。gcc需要在类名的前面直接加上&(我觉得很困惑,因为&运算符使用的是函数的地址,而不是类的地址)
kritzel_sw

我以为functionin void (aClass::*function)(int, int)是一种类型,因为它是in中的一种类型typedef void (aClass::*function)(int, int)
Olumide

@Olumide — typedef int X;定义一个类型;int X;创建一个对象。
皮特·贝克尔

11

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

现场演示

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


2

我问了一个类似的问题(C ++ openframeworks从其他类传递void),但是我发现的答案更清楚了,因此在这里为以后的记录做解释:

使用std :: function更容易,如:

 void draw(int grid, std::function<void()> element)

然后调用为:

 grid.draw(12, std::bind(&BarrettaClass::draw, a, std::placeholders::_1));

甚至更简单:

  grid.draw(12, [&]{a.draw()});

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


1
鉴于此标记为CPP,我认为这是最干净的解决方案。我们可以使用std::functionin 的常量引用,draw而不是在每次调用时都将其复制。
Sohaib

2
在此示例中不应使用占位符,因为该函数不包含任何参数。
kroiz

1

我将成员函数设为静态,并且可以正常工作:

#include <iostream>

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

void function1(int a,int b,void function(int, int))
{
    function(a, b);
}

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(10,12,test);
    function1(10,12,a.aTest); // <-- How should I point to a's aClass::test function?

    getchar();return 0;
}

不仅解决主要问题“如何将a的aClass :: test用作function1的参数?” 但也建议避免使用类外部的代码修改类私有变量
mathengineer

1

不知道为什么要通过这种极其简单的解决方案:

#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;
}

输出:

1 - 1 = 0
1 + 1 = 2

0

如果您实际上不需要使用该实例a (即您可以像@mathengineer的answer那样使其静态化),则只需传递一个非捕获的lambda即可。(衰减到函数指针)


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

int main()
{
   //note: you don't need the `+`
   function1(+[](int a,int b){return aClass{}.aTest(a,b);}); 
}

魔盒


注意:如果aClass构建成本高或有副作用,这可能不是一个好方法。


-1

您现在可以停止敲打头了。这是成员函数的包装程序,以纯C函数作为参数来支持现有函数。指令是这里的关键。thread_local

http://cpp.sh/9jhk3

// Example program
#include <iostream>
#include <string>

using namespace std;

typedef int FooCooker_ (int);

// Existing function
extern "C" void cook_10_foo (FooCooker_ FooCooker) {
    cout << "Cooking 10 Foo ..." << endl;
    cout << "FooCooker:" << endl;
    FooCooker (10);
}

struct Bar_ {
    Bar_ (int Foo = 0) : Foo (Foo) {};
    int cook (int Foo) {
        cout << "This Bar got " << this->Foo << endl;
        if (this->Foo >= Foo) {
            this->Foo -= Foo;
            cout << Foo << " cooked" << endl;
            return Foo;
        } else {
            cout << "Can't cook " <<  Foo << endl;
            return 0;
        }
    }
    int Foo = 0;
};

// Each Bar_ object and a member function need to define
// their own wrapper with a global thread_local object ptr
// to be called as a plain C function.
thread_local static Bar_* BarPtr = NULL;
static int cook_in_Bar (int Foo) {
    return BarPtr->cook (Foo);
}

thread_local static Bar_* Bar2Ptr = NULL;
static int cook_in_Bar2 (int Foo) {
    return Bar2Ptr->cook (Foo);
}

int main () {
  BarPtr = new Bar_ (20);
  cook_10_foo (cook_in_Bar);

  Bar2Ptr = new Bar_ (40);
  cook_10_foo (cook_in_Bar2);

  delete BarPtr;
  delete Bar2Ptr;
  return 0;
}

请对此方法的任何问题发表评论。

其他答案无法调用现有的普通C函数:http : //cpp.sh/8exun


3
因此,您不必使用std :: bind或lambda来包装实例,而要使用全局变量。与其他答案相比,我认为这种方法没有任何优势。
超级

@super,其他答案不能调用以纯C函数作为参数的现有函数。
Necktwi

2
问题是如何调用成员函数。传递免费功能已经在问题中适用于OP。您也没有传递任何东西。这里只有硬编码的函数和全局Foo_指针。如果要调用其他成员函数,该缩放比例如何?您将不得不重写基础功能,或对每个目标使用不同的功能。
超级
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.