【问题标题】:C++ member function pointer with different arguments - or is this bad anyway?具有不同参数的 C++ 成员函数指针 - 还是这很糟糕?
【发布时间】:2013-05-30 14:35:53
【问题描述】:

虽然我害怕你会告诉我这个话题已经讨论过好几次了,但我还是敢问,因为我无法产生解决方案。可能我只是在寻找错误的东西......

假设我有一个从某个外部函数接收“模式”的函数。根据模式,函数将调用同一对象的不同成员函数。对于没有任何参数的成员函数,这对我来说效果很好,但我没有找到如何将它扩展到有参数的成员。在现实世界的应用程序中,参数不是 int/float 而是更复杂的类,并且调用嵌套在不同的循环中,所以我需要多次放置我认为丑陋的 switch 语句。

问题 A:是否可以在现有设计的基础上轻松添加对带有参数的成员函数的支持?如果是,如何做到这一点?如果可能没有外部库...

问题 B:这是一种完全错误/糟糕的方法吗?我怎样才能做得更好?

非常感谢您的帮助和解释。

克里斯

标题摘录:

typedef void (Object::*memberFunction)();

class Object
{
    void memberFnNoArg();
    void memberFnWithIntArg(int arg);
    void memberFnWithFloatArg(float arg);
}

cpp 摘录:

void function()
{
    int mode = getModeFromSomewhere();

    int intArg = 33;
    float floatArg = 66.6;

    switch(mode)
    {
    case 1:
        process(&Object::memberFnNoArg);
        break;
    case 2:
        process(&Object::memberFnWithIntArg, ???); // how can I pass arg?
        break;
    case 3:
        process(&Object::memberFnWithFlaotArg, ???); // how can I pass arg?
        break;
    default:
        // do nothing;
    }

}

void process(Object::memberFunction func)
{
    Object object;
    // loops, called several times, ...
    (object.*func)(); // how do I handle different arguments?
}

【问题讨论】:

  • 也许std::bind 能帮上忙?
  • 也许使用模板?
  • 我不确定指向成员函数的指针如何简化您的设计。似乎将使用对象的算法变成函子可能是更好的方法。
  • @ben-voigt:这将简化我的设计,因为在 process() 内部,成员函数在算法内部的多个位置被调用。否则我将不得不将模式和其他参数传递给 process() 然后每次都进行区分。我正在寻找更透明的设计...

标签: c++ function-pointers argument-passing


【解决方案1】:

与其他答案相同,但为成员方法显示:

#include <iostream>
class Object
{
public:
    void memberFnNoArg()
    {
        std::cout << "Object::memberFnNoArg()\n";
    }

    void memberFnWithIntArg(int arg)
    {
        std::cout << "Object::memberFnWithIntArg(" << arg << ")\n";
    }

    void memberFnWithFloatArg(float arg)
    {
        std::cout << "Object::memberFnWithFloatArg(" << arg << ")\n";
    }
    bool memberFnWithBoolReturn(int)
    {
        return true;
    }
    template <typename... Args>
    void process(void (Object::*func)(Args...),Args... args);
    // overload process
    template <typename... Args>
    bool process(bool (Object::*func)(Args...),Args... args);
};
template <typename... Args>
void  process( void (Object::*func)(Args...),class Object* obj,Args... args)
{

    (obj->*func)(args...);
}
template <typename... Args>
bool  process( bool (Object::*func)(Args...),class Object* obj,Args... args)
{
    return ((obj->*func)(args...)) ;

}
int main()
{
    Object object;
    process(&Object::memberFnNoArg,&object);
    process(&Object::memberFnWithIntArg,&object,5);
    process(&Object::memberFnWithFloatArg,&object,2.7F);
    // overloaded process
    printf("%d\n",process(&Object::memberFnWithBoolReturn,&object,1));

    return 0;
}

【讨论】:

    【解决方案2】:

    将算法包装在函子中是正确的方法,std::function 是标准库提供的一个不错的函子。

    但是按照 Tomek 的建议,使用 boost::bind 甚至 std::bind 真的很难看 IMO,并且在绑定多个参数时会迅速失控。

    如果您有最新的编译器,则可以使用 lambda,这使得 Tomek 的示例看起来像:

    std::function<void(Object*)> f  =
        [](Object* const that){ that->memberFnNoArg(); };
    
    int int_value = 22;
    std::function<void(Object*)> f2 =
        [int_value](Object* const that){ that->memberFnIntArg(int_value); };
    
    Object o;
    f(&o);
    f2(&o);
    

    设置 lambda 有几个字符,但成员访问语法非常自然,并且很明显您如何进行更改。

    当然,如果你真的想要,你可以让参数成为对象的引用,但我更喜欢这里的指针。

    【讨论】:

    • 感谢这个非常优雅的建议!还有一个问题:(如何)我可以让它与指针参数一起工作吗?我收到错误“指针”未捕获。有没有类似 std::ref() 的指针?
    • @Chris:如果您想传递一个变量而不是文字常量的参数,请列出[] 之间的变量。这样 lambda 将存储其值以供以后在成员函数调用期间使用。
    【解决方案3】:

    看看 std::function 和 std::bind,它们似乎完全符合您的需要。

    编辑:

    std::function<void(Object &)> f = &Object::memberFnNoArg;
    std::function<void(Object &)> f2 = std::bind(&Object::memberFnWithIntArg, _1, 22);
    
    Object o;
    f(o);
    f2(o);
    

    据我所知,应该开箱即用。 这是你需要的吗?

    【讨论】:

    • 我很高兴听到 bind,因为这是我想过但不确定的一件事。目前,我正在努力只传递没有此指针的成员函数来绑定...我编辑了问题以说明问题:我希望能够在实例化对象之前传递要使用的成员函数。
    • @Chris:您可以绑定任何参数,而不仅仅是目标对象。
    • @Tomek:这看起来很有希望,但我仍然看不到如何处理带参数的成员函数。假设我在 switch 语句中进行了 std::function 赋值。然后我需要将该函数作为参数传递给“void process()”。我试图通过分配auto f1 = std::bind(&amp;Object::memberFnWithIntArg, 33); 来做到这一点,但 f1 的类型与 f 不同,所以我无法使用相同的进程函数处理 f、f1 和 f2...
    • @Chris:检查我的编辑。请注意,我不确定我是否正确(我不记得 std::bind 的确切用法)。您可能正在寻找的关键字是“占位符”。
    • 这里不需要使用mem_fn 或类似的吗?
    【解决方案4】:

    您可以使用可变参数模板函数:

    template <typename... Args>
    void process(void (Object::*func)(Args...),Args... args)
    {
        Object object;
    
        // loops, called several times, ...
        (object.*func)(args...);
    }
    

    这是一个完整的例子:

    #include <iostream>
    
    struct Object
    {
        void memberFnNoArg()
        {
          std::cout << "Object::memberFnNoArg()\n";
        }
    
        void memberFnWithIntArg(int arg)
        {
          std::cout << "Object::memberFnWithIntArg(" << arg << ")\n";
        }
    
        void memberFnWithFloatArg(float arg)
        {
          std::cout << "Object::memberFnWithFloatArg(" << arg << ")\n";
        }
    };
    
    template <typename... Args>
    void process(void (Object::*func)(Args...),Args... args)
    {
        Object object;
    
        // loops, called several times, ...
        (object.*func)(args...);
    }
    
    int main()
    {
      process(&Object::memberFnNoArg);
      process(&Object::memberFnWithIntArg,5);
      process(&Object::memberFnWithFloatArg,2.7F);
      return 0;
    }
    

    【讨论】:

      【解决方案5】:

      每个函数 (memberFn**) 不能是参数类的成员吗?

      class BaseArg
      {
        virtual void Fn() = 0;
      };
      
      class IntArg : public BaseArg
      {
        void Fn();
      };
      
      class FloatArg : public BaseArg
      {
        void Fn();
      };
      
      
      void function()
      {
          int mode = getModeFromSomewhere();
          BaseArg* pArg;
      
          if ( mode ... ){
            pArg = new IntArg( 33 );
          }
          else {
            pArg = new FloatArg( 66.6 );
          }
      
          pArg->Fn();  // Call the right function without a switch
                       // and without knowing the arguments
      
      }
      

      【讨论】:

        【解决方案6】:

        听起来像packaged_task。另请查看 Tomek 的建议。

        虽然 IRL 我会继续问很多问题,说明您为什么首先需要它。使用std::future 或其他更高级别的设施可能会更好地覆盖您的工作,

        【讨论】:

        • 感谢您的回答。也许我只是不明白,但我认为 packaged_task 和 future 正在帮助解决这个问题不涉及的多线程问题。
        • future 可以同步或异步启动——它的点可能非常接近您想要的,创建一个“订单”,稍后可以获取结果。它在两者之间所做的只是魔术。
        【解决方案7】:

        我看到的一种方法是使用可变参数(很像 printf,sprintf 就是这样做的)。 (或者可能使用 stdc 库,传递不同类型的列表。)

        原因是,参数列表是函数指针类型的一部分,因此您基本上需要一个带有可变参数的过程函数,然后 memberFunction 可能也需要是该类型之一。

        下面是一个简单的(非成员)示例,说明如何获取变量参数(成员函数的工作原理基本相同)。见stdarg.h

        typedef void (*var_function)(int typearg, ...);
        
        void print_arg(int typearg, ...)
        {
          va_list ap;
          int i;
        
          va_start(ap, typearg); 
        
          if (typearg==1) { // int 
             int i= va_arg(ap, int);
             printf("%d ", i);
          }
          else 
          if (typearg==2) { // float 
             float f= va_arg(ap, float);
             printf("%f ", f);
          }
          else 
          if (typearg==3) { // char *
             char *s= va_arg(ap, char *);
             printf("%s ", s);
          }
        
             ....
        
          va_end(ap);
        }
        
        // calling function with different types
        int main()
        {
           print_arg(1, 999);
           print_arg(2, 3.1415926);
           print_arg(3, "Hello");
           ....
           process(print_arg, 3, "via pointer);
        

        【讨论】:

        • 虽然它可以实现预期的效果,但我会在审查时“越过我的尸体”。
        • 感谢您的建议,但这个解决方案似乎并没有简化我的任务......它就像 switch 语句一样不透明。
        猜你喜欢
        • 2013-08-05
        • 2012-02-09
        • 1970-01-01
        • 1970-01-01
        • 2011-05-23
        • 1970-01-01
        • 2013-05-22
        • 2011-04-03
        相关资源
        最近更新 更多