指向C ++类方法的指针

时间:2020-03-05 18:51:19  来源:igfitidea点击:

在重构一些旧的C ++代码时,我发现我可以通过某种方式定义一个变量来删除一些重复的代码,该变量可以指向共享相同签名的任何类方法。经过一番挖掘,我发现我可以执行以下操作:

class MyClass
{
protected:
    bool CaseMethod1( int abc, const std::string& str )
    {
        cout << "case 1:" << str;
        return true;
    }

    bool CaseMethod2( int abc, const std::string& str )
    {
        cout << "case 2:" << str;
        return true;
    }

    bool CaseMethod3( int abc, const std::string& str )
    {
        cout << "case 3:" << str;
        return true;
    }

public:
    bool TestSwitch( int num )
    {   
        bool ( MyClass::*CaseMethod )( int, const std::string& );

        switch ( num )
        {
            case 1: CaseMethod = &MyClass::CaseMethod1;
                    break;
            case 2: CaseMethod = &MyClass::CaseMethod2;
                    break;
            case 3: CaseMethod = &MyClass::CaseMethod3;
                    break;
        }

        ...

        bool res = CaseMethod( 999, "hello world" );

        ...

        reurn res;
    }
};

我的问题是这是解决此问题的正确方法吗?我应该考虑Boost提供的任何东西吗?

编辑...

好的,我的错误我应该像这样调用方法:

bool res = ( (*this).*CaseMethod )( 999, "Hello World" );

解决方案

回答

尽管CaseMethod调用不正确(它是指向成员函数的指针,所以我们必须指定应在其上调用该方法的对象),但是我们当然可以做到。正确的调用如下所示:

bool res = this->*CaseMethod( 999, "hello world" );

另一方面,我建议使用boost :: mem_fn,这样我们将有更少的机会搞砸它。 ;)

回答

我们所拥有的是一个指向成员函数的指针。它将解决问题。使" TestSwitch"函数编译,使我感到惊讶,因为调用语法与我们期望的语法略有不同。它应该是:

bool res = (this->*CaseMethod)( 999, "hello world" );

但是,我们可能会发现boost :: function和boost :: bind的组合使事情变得容易一些,因为可以避免使用奇怪的调用语法。

boost::function<bool(int,std::string)> f=
    boost::bind(&MyClass::CaseMethod1,this,_1,_2);

当然,这会将其绑定到当前的" this"指针:如果我们愿意,可以使成员函数的" this"指针成为显式的第三个参数:

boost::function<bool(MyClass*,int,std::string)> f=
    boost::bind(&MyClass::CaseMethod1,_1,_2,_3);

另一种选择是使用虚函数和派生类,但这可能需要对代码进行重大更改。

回答

我们在这里给出的本地化示例没有本质上的错误,但是如果在更广泛的上下文中使用它们(例如,在类外部,它们是该类的指针)与复杂的继承树结合。编译器通常管理方法指针的方式与"普通"指针不同(因为除了代码入口点以外,还有其他信息),因此,我们对它们的操作有很多限制。

如果我们只是按照描述的方式保持简单的指针,那么我们会很好,但是在更复杂的用途上,我们可能希望看一下更通用的函子系统,例如boost :: bind。它们可以将指针指向几乎所有可调用的代码指针,并且在必要时还可以绑定实例化函数参数。

回答

我看不出调用与仅在switch语句中调用方法之间的区别。

不,没有语义或者可读性差异。

我看到的唯一区别是我们正在使用指向方法的指针,因此禁止编译器内联它或者优化对该方法的任何调用。

回答

我们还可以构建一个查找(如果键范围是合理的),以便最终编写:

this->*Methods[num]( 999, "hello world" );

这也将删除该开关,并使清理工作更有价值。

回答

没有更广阔的背景,很难找到正确的答案,但是我在这里缝了三种可能性:

  • 保持正常的switch语句,无需执行任何操作。这是最可能的解决方案
  • 如@Simon所说,可以将指向成员函数的指针与数组结合使用,也可以与地图一起使用。对于具有大量案例的案例陈述,这可能会更快。
  • 将类分为多个类,每个类都带有一个要调用的函数,并使用虚函数。这可能是最好的解决方案,购买它需要认真考虑。考虑诸如州或者访客之类的GoF模式。

回答

还有其他可用的方法,例如使用抽象基类或者专用模板函数。

我将描述基类的想法。

我们可以定义一个抽象基类

class Base { virtual bool Method(int i, const string& s) = 0; };

然后将每个案例写为一个子类,例如

class Case1 : public Base { virtual bool Method(..) { /* implement */; } };

在某个时候,我们将获得" num"变量,该变量指示要执行的测试。我们可以编写一个使用此num的工厂函数(我称其为which_case),并返回一个指向Base的指针,然后从该指针调用Method。

Base* CreateBase(int which_num) { /* metacode: return new Case[which_num]; */ }
// ... later, when you want to actually call your method ...
Base* base = CreateBase(23);
base->Method(999, "hello world!");
delete base;  // Or use a scoped pointer.

顺便说一句,这个应用程序让我希望C ++支持静态虚拟函数,或者像" type"这样的内置类型,但事实并非如此。