有没有办法在没有。*或 - > *运算符的情况下调用成员函数

时间:2013-10-09 10:26:13

标签: c++ boost cdecl

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

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

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

在我看来,在一天结束时(在汇编/二进制级别),类的所有成员函数都是普通函数,它们应该在n + 1个参数上运行,其中(+ 1代表this

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

  1. 类对象本身(指向名为this的D类对象的指针)
  2. int
  3. 那么,有没有办法(或者黑客)调用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)) ();
    

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

11 个答案:

答案 0 :(得分:19)

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

Evil.h:

#ifdef __cplusplus
extern "C" {
#endif

struct MyStruct
{
#ifdef __cplusplus
    MyStruct();

    void method(int);
#endif
};

#ifdef __cplusplus
}
#endif

Evil.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; }

Evil.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。这篇文章提供了一个如何做到这一点的例子(或者更确切地说,为什么你真的不应该这样做的一个例子!)

  

所以,有没有办法(或者hack)调用D :: foo,并将类对象作为函数参数传递给它而不是使用。或 - &gt;或。*或 - &gt; *类对象上的运算符?

特定于平台,令人厌恶的肮脏黑客......

答案 1 :(得分:14)

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

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

bind没有做任何“魔法”;在其实施的某个地方,它确实如此。

  

在一天结束时它是一个带有两个参数的函数

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

答案 2 :(得分:4)

我看到的唯一方法是用行为类似函数的类替换你的typedef-ed函数指针。

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

在下面的代码中,我使用一个宏来键入一个模板,然后像你描述的那样操作。

#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;
 }

答案 3 :(得分:3)

  

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

没有汇编的两种方式

#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;
}

答案 4 :(得分:2)

  

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

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

答案 5 :(得分:2)

是的,C ++ 11标准中有一个名为 INVOKE 的抽象,它适用于Callable对象。指向成员函数(PTMF)对象是可调用的,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

答案 6 :(得分:1)

是的,确实有足够的扭曲你可以逃避“新”成员函数语法,但我不得不问:你为什么这样做?语法促进了对成员函数以某种方式从周围对象调用的理解。鉴于虚函数的存在,这实际上(并且必须是)情况。它还自动化了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和朋友们正在做的非常相似,但通常更快(但显然,便携性较差)。

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

  

__ thiscall调用约定用于成员函数,而且是   这样做的C ++成员函数使用的默认调用约定   不要使用变量参数。在__thiscall下,被调用者清理   堆栈,这对vararg函数来说是不可能的。争论被推了   在堆栈中从右到左,这个指针被传递   通过寄存器ECX,而不是堆栈,在x86架构上。

那么,你究竟不喜欢什么:someInstance-&gt; someFunction()?

答案 7 :(得分:0)

  

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

     

...

     

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

简而言之,没有。如果不以某种方式指定调用左侧的对象,则无法调用非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);

答案 8 :(得分:0)

该标准非常清晰,完全明确。第5.2.2节(C ++ 11)第一句规定:

  

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

就是这样 - 它们是不同的东西,你不能混淆它们。

答案 9 :(得分:0)

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

#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 w / o a。或 - &gt;,以下将起作用

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 :(得分:0)

虽然我觉得它有点奇怪,但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;
}