将非静态成员函数作为参数传递

4
我有许多X类的实例,每个实例都包含另一个CB类(有时不止一个CB)。CB类中有一个函数指针,该指针将在某些事件(单击、定时器等)之后被调用。

如果我可以将实例成员函数传递给本地CB类实例(在这种情况下传递say()函数),那就太有用了,这样一旦CB触发函数,它就只会修改自己的实例。
示例代码如下:
 #include <iostream>                                                                                                     

 class CB {                                                                                                
     void (*function)(void);                                                                                             

     void init(void (*function)(void)) {                                                                                 
         this->function = function;                                                                                      
     }                                                                                                                   
 };                                                                                                                      

class X {                                                                                                               
    private:                                                                                                            
        CB cb;                                                                                                          

    public:                                                                                                             
        void init() {                                                                                                   
            cb.init( &(this->say) ); //<- Here's the problem                                                            
        }                                                                                                               

        void say() {                                                                                                    
            std::cout << "Hello World!" << std::endl;                                                                   
        }                                                                                                               
};                                                                                                                      

int main() {                                                                                                            
    X x;                                                                                                                
    x.init();                                                                                                           
} 

所以我的上面的示例在第17行失败,出现错误:
no known conversion for argument 1 from ‘void (X::*)()’ to ‘void (*)()’

有没有可能以某种方式将实例的本地say()函数传递给实例的CBCB函数无法修改,不幸的是它将被其他类型的类使用。
我在其他帖子中读到,除非say()函数是static或存在于类外部(这意味着它将无法访问实例变量),否则这很可能是不可能的。
有可能让这个工作像我所想的那样吗?如果不能,最常见的解决方案是什么(这似乎是很多人都会遇到的问题)?

你知道如何编写构造函数吗? - Ryan Haining
你最好希望在CB调用试图修改X状态的方法时,你的X实例仍然存在。 - Cory Kramer
为什么需要函数指针?使用虚函数(接口)代替即可。 - πάντα ῥεῖ
void init(void (CB::*function)(void)) - Captain Obvlious
1
可能是[C ++,成员函数的函数指针]的重复问题(https://dev59.com/IHE95IYBdhLWcg3wOLU1)。 - Captain Obvlious
显示剩余4条评论
2个回答

1
这里有一种方法可以获得类似的结果,但仍然有效:
#include <iostream>
#include <functional>

class CB
{
public:
    std::function<void(void)> function;

    template <class F>
    CB(F function) : function(function) { }

    void operator()() { function(); }
};

class X
{
private:
    CB cb;

public:
    X() :cb([this] {say(); }) { }

    void say() { std::cout << "Hello World!\n";  }

    void operator()() { cb(); }
};

int main() {
    X x;
    x();
}

我稍微改写了CB。一些内容与现有代码不兼容,但这是因为现有设计并不是一个好主意。现在你正在进行通常被称为两阶段初始化的操作,你必须先创建一个对象,然后调用它的init()方法,然后该对象才能真正准备好使用。这通常是不被赞同的。

我还将CB更改为存储std::function,它可以用于存储指向函数的指针,或者几乎任何可以像函数一样调用的东西。

然后我添加了一个operator()来实际调用存储的类似函数的对象。

X中我也做了一些更改,最大的变化是使用lambda表达式生成一个可以像函数一样调用的对象。在构造过程中将其传递给嵌入式的CB实例。

所以序列是外部代码创建一个X对象。这将创建一个嵌入式的CB类实例,并传递一个闭包来调用X::say。然后我们调用X::operator(),这将调用CB::operator(),进而调用X::say()


不清楚 class CB 实际上添加了什么价值...似乎可以摆脱它并使用 std::function<void(void)> cb; - Ben Voigt
@BenVoigt:这很有可能——我留下了它,猜测在现实生活中它也可能会做更多/其他的事情。 - Jerry Coffin
在2021年尝试过,效果非常好!在找到这个答案之前,我只能将静态方法作为参数传递给另一个类的构造函数。 - spaL

1

“是否有办法将实例的本地say()函数传递给实例的CB?”

如果您确定需要一个函数指针,请改用std::function包装器:

class CB {
    // void (*function)(void); Replace this with:
    std::function<void,void> fn_;
public:                                                                                  
    // void init(void (*function)(void)) { 
    //     this->function = function;
    // } Replace with a constructor
    CB(const std::function<void,void>& fn) : fn_(fn) {}
};

如果您需要将其绑定到特定的类实例,请使用std::bind
class X {
     CB cb_;
public:
    // Again use a constructor with a member initializer list
    void X() : cb_(std::bind(&X::say, this) {}

    void say() {
        std::cout << "Hello World!" << std::endl;
    }
};

如果是这样的话,你可能有点迷失了。因为回调函数签名没有提供某种用户数据参数,所以不可能提供一个包含对特定实例引用的包装器回调提供程序。
在您的真实代码中,该回调函数是否真的具有void fn(void);的签名,或者它是否提供一些参数,允许绑定到任何数据实例,例如void fn(void* userData);
你仍然可以将class Xstatic成员函数作为回调函数。如果那个say()函数不改变X的状态(就像你的示例中一样),将其改为static并不是一个问题(但我担心这并不是你在寻找的):
class CB {
    void (*fn_)(void);
public:
    CB(void (*fn)(void)) : fn_(fn) {}
};

class X {
     CB cb_;
public:
    // Again use a constructor with a member initializer list
    void X() : cb_(&X::say) {}

    static void say() {
 // ^^^^^^
        std::cout << "Hello World!" << std::endl;
    }
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接