【问题标题】:Is there a way to call member function without .* or ->* operator有没有办法在没有 .* 或 ->* 运算符的情况下调用成员函数
【发布时间】:2013-10-09 10:26:12
【问题描述】:

下面通过指向成员函数调用D::foo函数的方法会产生错误:必须使用.*->*在'f(...)中调用指向成员函数' .. 当然,这不是我们调用指向成员函数的方式。

正确的调用方式是(d.*f)(5); OR (p->*f)(5);

我的问题是,'有没有办法在没有左侧类对象的情况下调用类的成员函数?我想知道我们是否可以将类对象 (this) 作为常规参数传递?

在我看来,归根结底(在汇编/二进制级别)类的所有成员函数都是正常函数,它们应该对 n + 1 个参数进行操作,其中(+1 代表this

如果我们在下面讨论D::foo 函数,在汇编/二进制级别它应该对两个参数进行操作:

  1. 类对象本身(指向名为this的D类对象的指针)
  2. int

那么,有没有办法(或破解)调用 D::foo 并将类对象作为函数参数传递给它,而不是在类对象上使用 . or -> or .* or ->* 运算符?

示例代码:

#include <iostream>
using namespace std;

class D {
    public:
        void foo ( int a ) {
            cout << "D" << endl;
        }

        int data;
};


//typedef void  __cdecl ( D::*  Func)(int);
typedef void ( D::*  Func)(int);

int main ( void ) 
{
    D d;

    Func f = &D::foo;
    f(&d, 5);

    return 1;
 }

一种方法是使用增强绑定,即

(boost:: bind (&D::foo, &d, 5)) ();

编辑: "请注意,我不是在寻找这个程序的有效版本,我知道如何让它工作"

【问题讨论】:

  • 看这个question的答案
  • 没有“魔法”。在bind 结果类型的operator() 中的某处将类似于(ptr-&gt;*fun)(arg);您只是看不到它,因为它隐藏在库实现中。
  • 这不是“人为限制”,就像在声明后要求 ; 是“人为限制”一样。这只是语言的语法。
  • " 归根结底,它是一个函数,它接受两个参数,第一个是对 D 类对象本身的引用(this),第二个是'int'。 ——不,不是。归根结底,它是一个接受参数值和this 值的函数,不一定按此顺序。非静态成员函数很可能使用与非成员函数不同的调用约定。因此,它不一定与以this 作为第一个参数调用常规函数相同,因为this 可能会在与通常传递第一个参数的位置不同的寄存器或堆栈槽中传递。
  • @Mike Seymour:Bind 不会进行调用,它只是让它提升函数对象。任何带有签名 arg0> D& 和 arg1> int 的函数都可以调用它。这就是我所说的魔法。

标签: c++ boost cdecl


【解决方案1】:

您真的想在不使用.-&gt; 的情况下调用成员函数吗?真的,真的?好吧,好吧……

邪恶.h:

#ifdef __cplusplus
extern "C" {
#endif

struct MyStruct
{
#ifdef __cplusplus
    MyStruct();

    void method(int);
#endif
};

#ifdef __cplusplus
}
#endif

邪恶.cc:

#include <iostream>

#include "evil.h"

MyStruct::MyStruct() { std::cout << "This is MyStruct's constructor" << std::endl; }

void MyStruct::method(int i) { std::cout << "You passed " << i << std::endl; }

邪恶.c:

#include "evil.h"

int main()
{
    struct MyStruct my_struct;
    _ZN8MyStructC1Ev(&my_struct); /* MyStruct::MyStruct() */

    _ZN8MyStruct6methodEi(&my_struct, 3); /* MyStruct::method(int) */

    return 0;
}

这恰好适用于我在 Linux 上的 gcc 和 g++ 组合,但不用说它依赖于平台 ABI,并且在调用 下划线-大写字母形式的函数时违反了 C89 标准.几乎可以肯定它不适用于虚函数,我也不想尝试。这也可能是我写过的最邪恶的东西。不过还是……


编辑:引用 OP:

在我看来,归根结底(在汇编/二进制级别)类的所有成员函数都是普通函数,它们应该对 n + 1 个参数进行操作,其中(+1 代表this

虽然自 CFront 以来的每个编译器都采用这种方式,但这只是一个实现细节。 C++ 标准苦于指定成员函数应该如何实现,只是它们应该如何表现。

因为它是一个实现细节,不同的平台以不同的方式来实现它。这不仅仅是名称修改。例如,Linux 上使用的调用约定指定 this 作为第一个参数传递;其他实现(Borland,IIRC?)将this 作为 last 参数传递。

因此,如果您想将成员函数视为带有额外 this 的普通函数,那么您必须将自己限制为特定的 ABI。这篇文章提供了一个示例,说明您可以如何做到这一点(或者更确切地说,一个示例说明您为什么真的不应该这样做!)

那么,有没有办法(或破解)调用 D::foo 并将类对象作为函数参数传递给它,而不是使用 .或 -> 或 .* 或 ->* 类对象上的运算符?

一个特定于平台的、令人作呕的肮脏黑客...

【讨论】:

  • 似乎您已经使用nm 直接从二进制文件中提取了损坏的符号名称。这不是我们想要的。
  • 同意,问题不清楚,这个答案正在调用它(除了第二次调用,没有 int 值,在里面......)
  • big +1 用于告诉“this”作为不可见的第一个参数是一个实现细节,而不是由标准指定。另外提供一个可行的解决方案。
  • @ams:我也觉得这是我能做到的最好的。
  • @TristanBrindle:我已经将我的赏金奖励给了这个答案,因为我理解了这个问题并给予了足够的帮助(主要用于编辑部分:))。我还要感谢您为撰写此答案所付出的时间和努力。
【解决方案2】:

我不太确定你在问什么。通过指向成员的指针调用函数没有“人为限制”;你只需要使用正确的语法:

(d.*f)(5);  // for objects or references
(p->*f)(5); // for pointers

bind 不会做任何“魔法”;在其实现的某个地方,它正是这样做的。

在一天结束时,它是一个接受两个参数的函数

不,它是一个接受一个参数的成员函数,并在一个对象上调用。虽然在概念上类似于带有两个参数的函数,但有一个很大的区别:成员函数可以是虚拟的,涉及运行时分派机制。

【讨论】:

    【解决方案3】:

    我看到的唯一方法是将你的 typedef 函数指针替换为一个行为像函数的类。

    您正在寻找这样的东西吗?

    在下面的代码中,我使用了一个宏来定义一个模板,然后该模板的运行方式与您描述的函数一样。

    #include <iostream>
    using namespace std;
    
    class D {
        public:
            void foo ( int a ) {
                cout << "D" << endl;
            }
    
            int data;
    };
    
    template<class Object, class Param, class Function>
    class FunctionPointerHelper                                           
    {                                                    
    private:                                             
        Function m_function;                             
    public:                                              
        FunctionPointerHelper(Function function) :                        
            m_function(function)                         
        {                                                
        }                                                
        void operator=(Function function)                
        {                                                
            m_function = function;                       
        }                                                
        void operator()(Object& object, Param param) const      
        {                                                
            (object.*m_function)(param);                 
        }                                                
    };
    
    #define TYPEDEF_NICE_FUNCTION(RESULT, CLASS, NEW_TYPENAME, PARAM) \
    typedef RESULT ( CLASS::* NEW_TYPENAME_INTERMEDIATE)(PARAM) ; \
    typedef FunctionPointerHelper<CLASS, PARAM, NEW_TYPENAME_INTERMEDIATE> NEW_TYPENAME;
    
    TYPEDEF_NICE_FUNCTION(void, D, Func, int)
    
    int main ( void ) 
    {
        D d;
    
        Func f = &D::foo;
        f(d, 5);
    
        return 1;
     }
    

    【讨论】:

      【解决方案4】:

      有没有办法在左侧没有类对象的情况下调用类的成员函数?

      无需组装的两种方式

      #include <iostream>
      using namespace std;
      
      class D {
          public:
              void foo ( int a ) {
                  cout << "D" << endl;
              }
      
          static void foo(D& d, int a)
          {
              d.foo(a);
          }
      
              int data;
      };
      
      void foo(D& d, int a)
      {
          d.foo(a);
      }
      
      int main ( void ) 
      {
          D d;
      
          D::foo(d, 5);
          foo(d, 5);
      
          return 0;
      }
      

      【讨论】:

        【解决方案5】:

        但必须有一种方法来避免这种人为的限制 由c++强加

        “人为限制”是什么意思?这只是语言定义的语法。它出什么问题了? bind() 的“魔力”将在内部使用 -&gt;* 运算符来调用函数。

        【讨论】:

        • 这是人为的,因为 f(D, 5) 是通过指向成员函数的指针调用的合理语法,即使语言定义朝着不同的方向发展。
        • @PeteBecker 我更倾向于遵循史蒂夫杰索普评论中的观点,它可能并不完全等同。 'artificial' 可能是 C 程序员意义上的。
        • 在考虑不存在的事物的属性时必须非常小心。语言不能提供具有确切和明显含义的语法并没有内在的原因,在这种情况下,它将是完全等价的。
        【解决方案6】:

        是的,C++11 标准中有一个名为 INVOKE 的抽象,它适用于 Callable 对象。成员函数指针 (PTMF) 对象是 Callable 对象,this 作为第一个(常规)参数传递。

        没有std::invoke 功能,尽管已经提出。要从任何 Callable 对象中获取函子,您可以使用 std::bind,它是 Boost.bind 的派生词。

        这很好用:

        int main ( void ) 
        {
            D d;
        
            auto f = std::bind( &D::foo, _1, _2 );
            f(&d, 5);
         }
        

        http://ideone.com/VPWV5U

        【讨论】:

        • 请您再详细说明一下。
        • @VishnuKanwar 什么部分?这是一个标准工具,类似于您所说明的 Boost Bind,但新的 auto 关键字使声明易于使用的本地对象变得容易。
        【解决方案7】:

        是的,确实,只要有足够的扭曲,你就可以避开“新”成员函数的语法,但我不得不问:你为什么要这样做?该语法促进了对成员函数以某种方式从周围对象调用的理解。鉴于虚函数的存在,这实际上是(并且必须是)情况。它还可以自动化 this 指针更改调用虚函数(并支持虚继承)的要求。

        要了解如何执行此操作,请查看 FastDelegate 库的实现。它的实现需要知道编译器的成员函数指针的二进制结构(其中可以有几种变体)。这就是您正在寻找的“黑客”。 FastDelegate 的委托(即闭包)在调用点变成两条指令:将计算出的“this”值拉到适当的位置(基于调用约定)并间接跳转到实际函数的入口地址。

        这最终看起来像这样:

        fastdelegate::FastDelegate0<> functionObject;
        SomeClass someInstance;
        
        //fill in object and function information in function object
        functionObject.bind(&someInstance,&SomeClass::someFunction);
        
        //call function via object.  Equivalent to: someInstance->someFunction();
        functionObject();
        

        这与 boost::bind 和朋友们正在做的非常相似,但通常更快(虽然,很明显,便携性较差)。

        在此处使用的模板和运算符重载下面有一些数学运算(在绑定函数内部),它计算出如何将 &someInstance 更改为 SomeClass::someInstance 所需的 this 指针。它还找到底层函数的实际地址并记录这两个值以备后用。当调用发生时,它会通过使用-&gt;* 的一些技巧来强制编译器“做正确的事情”。但是,如果您真的想避免甚至依赖 -&gt;* 运算符,那么您可以在那时进行一些类型转换并将指针转换为“__thiscall”函数指针(好吧,假设您在 Windows 上):

        __thiscall 调用约定用于成员函数,并且是 C++ 成员函数使用的默认调用约定 不使用可变参数。在 __thiscall 下,被调用者清理 堆栈,这对于可变参数函数是不可能的。参数被推送 在堆栈上从右到左,传递 this 指针 在 x86 架构上通过寄存器 ECX,而不是在堆栈上。

        那么,你不喜欢的到底是什么:someInstance->someFunction()?

        【讨论】:

        • “那么,你不喜欢的到底是什么:someInstance->someFunction()?”在某种意义上是的。如果您想重新阅读它,我已经让我的问题更清楚了。谢谢。
        • 我仍然不知道为什么你想这样做。您是否尝试从 c 代码调用某些 C++ 库?如果是这样,请停止尝试这样做并使用导出 c 可调用函数的 c++ 包装器。
        【解决方案8】:

        该标准在这个主题上异常清晰且完全不含糊。第 5.2.2 节(C++11)的第一句指出:

        函数调用有两种:普通函数调用和成员函数调用。

        就是这样——它们是不同的东西,你不能把它们混在一起。

        【讨论】:

          【解决方案9】:

          这是您的程序的一个有效版本。指向类方法的指针需要一些复杂的调用,如下所示:

          #include <iostream>
          using namespace std;
          
          class D {
              public:
                  void foo ( int a ) {
                      cout << "D" << endl;
                  }
          
                  int data;
          };
          
          
          typedef void ( D::*  Func)(int);
          
          int main ( void ) 
          {
              D d;
              D *pThis = &d;
          
              Func f = &D::foo;
          
              /* invoke function pointer via a "class" pointer */
              (pThis->*f)(5);
          
              /* invoke function pointer via a "class" object */
              (d.*f)(5);
          
              return 1;
          }
          

          这个程序的输出粘贴如下:

          ~/prj/stackoverflow
          # g++ funcptr.cpp 
          
          ~/prj/stackoverflow
          # ./a.out 
          D
          D
          

          编译器通过类方法指针为方法调用提供“this”指针。 “this”指针将是调用对象。

          当然有办法。将 foo 设为静态类方法,并将 foo 的签名更改为采用“this”指针。

          发件人:

          void foo(int a) {}
          

          收件人:

          static void foo(D *pthis, int a) {}
          

          调用 foo 不带 .或->,以下将起作用

          int main(void)
          {
              D d;
              int a = 0;
              D::foo(&d, a); /* call foo, w/o using . or -> and providing your own this pointer */
              return 0;
          }
          

          【讨论】:

          • 谢谢。但我不是在寻找一个工作程序。请重新阅读问题说明。
          【解决方案10】:

          有没有办法在左侧没有类对象的情况下调用类的成员函数?

          ...

          那么,有没有办法(或破解)调用 D::foo 并将类对象作为函数参数传递给它,而不是在类对象上使用 .-&gt;.*-&gt;* 运算符?

          简而言之,没有。如果不以某种方式指定调用左侧的对象,则不能调用非static 类成员函数。如果未指定类(用于静态函数)或对象(用于非静态),编译器无法知道您尝试调用哪个函数。它在当前范围之外,因为它在类或对象内。有一些方法可以“破解”代码,因此您可以将其编写为在您的示例中看起来像 main()。其他一些答案给出了这类黑客的例子。

          ------------使用静态函数------

          类中的函数可以在类外调用而无需指定类对象。这样做的方法是为函数指针创建一个typedef,其签名与类函数的签名相匹配,然后创建这样一个指针并为其分配类函数的地址。然后可以调用该函数,而无需在左侧指定类或对象。

          函数必须static。 ISO C++ 不允许使用绑定成员函数的地址来形成指向该函数的指针,即您不能从类对象创建指向非static 成员函数的指针。您也许可以找到一些不符合 ISO 标准的编译器,但我没有任何关于这方面的指导。

          this 到非static 类成员函数的参数是隐含参数,并且始终是隐含参数。您无法手动指定它,但您可以通过传递指向类对象的指针来模拟它。

          所以,对于您的代码示例:

          • void D::foo(int) 应该是 static 并添加了 D* 参数:class D { static void D::foo(D*, int); };
          • 应更改typedef void (D::* Func)(int); 以适应先前的更改并删除D::typedef void (*Func)(D*, int);

          【讨论】:

          • @VishnuKanwar 我重新阅读以找到您的问题并添加到我的回复中以回答它。
          • 静态成员函数无论如何都独立于类对象。这不是我要找的;对不起。
          • 好的。就像我在评论中所说的那样,ISO C++ 不允许指向非静态对象函数的指针。您必须为自己找到一个不符合 ISO 规范的编译器。 (或者自己写!)
          【解决方案11】:

          虽然我认为想要它是一种奇怪的东西,但 call_member 看起来就像你想要的。 (需要 C++11)

          #include <iostream>
          
          template<typename T, typename Ret, typename... Args>
          Ret call_member(Ret (T::*mem)(Args...), T* obj, Args... args)
          {
              return (obj->*mem)(args...);
          }
          
          struct S {
              void foo(int i) { std::cout << "Foo: " << i << '\n'; }
          };
          
          int main()
          {
              S s;
          
              call_member(&S::foo, &s, 5);
              return 0;
          }                                                                
          

          【讨论】:

          • 这里-&gt;*用于return (obj-&gt;*mem)(args...);
          • 啊,我以为你只是想要语法糖。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-06-14
          • 2022-07-13
          • 1970-01-01
          • 2022-09-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多