7

我需要能够让一个超类执行由继承自它的类定义的回调。我对 C++ 比较陌生,据我所知,成员函数指针的主题似乎是一个非常模糊的领域。

我已经看到了问题的答案和讨论各种事情的随机博客文章,但我不确定他们中是否有任何人在这里专门处理我的问题。

这是一个简单的代码块,说明了我正在尝试做的事情。该示例可能没有多大意义,但它准确地类似于我正在尝试编写的代码。

class A {
    protected:
    void doSomething(void (A::*someCallback)(int a)) {
        (*this.*someCallback)(1234);
    }
};

class B : public A {
    public:
    void runDoIt() { doSomething(&B::doIt); }
    void runDoSomethingElse() { doSomething(&B::doSomethingElse); }
    protected:
    void doIt(int foo) {
        cout << "Do It! [" << foo << "]\n";
    }
    void doSomethingElse(int foo) {
        cout << "Do Something Else! [" << foo << "]\n";
    }
};

int main(int argc, char *argv[]) {
    B b;
    b.runDoIt();
    b.runDoSomethingElse();
}
4

4 回答 4

8

问题是 的成员函数B不是 的成员函数A,即使B派生自A。如果您有void (A::*)(),则可以在 any 上调用它A *,而不管指向的对象的实际派生类型如何。

(当然,同样的原则也适用于 a A &。)

假设B派生自A,并C派生自A;如果可以将 a void (B::*)()(say) 视为 a void (A::*)(),则可以执行以下操作:

A *c=new C;
A *b=new B;
void (A::*f)()=&B::fn;//fn is not defined in A
(c->*f)();

的成员函数B将在类型的对象上调用C。结果充其量是不可预测的。

基于示例代码,并假设不使用 boost 之类的东西,我倾向于将回调构造为一个对象:

class Callback {
public:
    virtual ~Callback() {
    }

    virtual Do(int a)=0;
};

然后调用回调的函数以某种方式获取这些对象之一,而不是普通的函数指针:

class A {
protected:
    void doSomething(Callback *c) {
        c->Do(1234);
    }
};

然后,您可以为每个您有兴趣调用的派生函数设置一个回调。以 doIt 为例:

class B:public A {
public:
    void runDoIt() {
        DoItCallback cb(this);
        this->doSomething(&cb);
    }
protected:
    void doIt(int foo) {
        // whatever
    }
private:
    class DoItCallback:public Callback {
    public:
        DoItCallback(B *b):b_(b) {}

        void Do(int a) {
            b_->doIt(a);
        }
    private:
        B *b_;
    };
};

减少样板文件的一个明显方法是将成员函数指针放入回调中,因为派生的回调可以自由地处理特定类型的对象。这将使回调更通用一点,因为当回调时,它将调用 B 类型对象上的任意成员函数:

class BCallback:public Callback {
public:
    BCallback(B *obj,void (B::*fn)(int)):obj_(obj),fn_(fn) {}

    void Do(int a) {
        (obj_->*fn_)(a);
    }
private:
    B *obj_;
    void (B::*fn_)(int);
};

这将使 doIt 像这样:

void B::runDoIt() {
    BCallback cb(this,&B::doIt);
    this->doSomething(&cb);
}

这可能会被“改进”,尽管并非所有读者都可以通过以下方式看到它:

template<class T>
class GenericCallback:public Callback {
public:
    GenericCallback(T *obj,void (T::*fn)(int)):obj_(obj),fn_(fn) {}

    void Do(int a) {
        (obj_->*fn_)(a);
    }
private:
    T *obj_;
    void (T::*fn_)(int);
};

使用它,上面的 runDoIt 函数可以变成:

void B::runDoIt() {
    GenericCallback<B> cb(this,&B::doIt);
    this->doSomething(&cb);
}

(通用回调也可以在成员函数指针本身上进行模板化,尽管在大多数情况下这不太可能提供任何实际优势。它只是更多的输入。)

我发现以这种方式构建事物效果很好,因为它不需要继承。因此,它对要回调的代码几乎没有限制,这在我看来总是一件好事,而且我发现这超过了难以完全消除的冗长。不可能根据示例来判断这种方法是否真的适合,但是......

于 2009-03-14T01:54:45.093 回答
7

如果您可以使用 boost 库,我建议您使用 boost::function 来完成手头的任务。

class A {
public:
   void doSomething( boost::function< void ( int ) > callback )
   {
      callback( 5 );
   }
};

然后任何继承(或外部类)都可以使用 boost::bind 进行调用:

class B {
public:
   void my_method( int a );
};
void test()
{
   B b;
   A a;
   a.doSomething( boost::bind( &B::my_method, &b, _1 ) );
};

我没有检查确切的语法,而是从头顶输入了它,但这至少接近正确的代码。

于 2009-03-14T00:26:01.467 回答
2

为了您的目的,C++ 提供了比成员函数指针更好的构造。(一般来说,您不必在正确编写的纯 C++ 中使用函数指针)。我的脑海中有两个选择:

1) 在 A 上创建一个名为 doIt() 的纯虚函数。从 doSomething() 调用 doIt()。在 B 上,保留一个枚举成员变量来指示您想要做什么,并在调用 doSomething() 之前设置它。覆盖 B 中的 doIt(),其实现将成员枚举切换/封装到您希望运行的底层代码。

2) 使用单个 doIt() 纯虚方法创建一个 DoInterface 类。为您的 Class B 创建它的派生类,它有一个指向拥有 B 实例的指针,并在您的每个 DoInterface 实现中实现 doIt() 调用 B 上的适当函数。doSomething() 将 DoInterface 的实例作为参数。这称为回调对象模式。

于 2009-03-14T00:37:59.210 回答
0

您试图将派生类中定义的方法调用为方法基类。
但是这个函数没有在基类中定义。

于 2009-03-14T00:34:18.280 回答