有没有一种方法可以在不使用 .* 或 ->* 运算符的情况下调用成员函数?

11

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

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

我的问题是,“有没有一种方法可以在左手边不使用类对象来调用类的成员函数?我想知道是否可以像普通参数一样传递类对象 (this) 作为参数?”

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

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

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

那么,有没有一种方法(或黑客技巧)可以使用作为函数参数传递给它的类对象而不是在类对象上使用 . 或 -> 或 .* 或 ->* 运算符来调用 D::foo

示例代码:

#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绑定,即

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

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


1
看一下这个问题的答案。 - Mario
4
“没有什么‘魔法’。在bind结果类型的operator()中,会有类似于(ptr->*fun)(arg)这样的内容;你看不到它是因为它被埋在库的实现中。” - Mike Seymour
1
这并不是"人为限制",就像在语句后需要加;一样,并非"人为限制"。这只是语言的语法结构。 - Angew is no longer proud of SO
1
“说到底,这是一个函数,它接受两个参数。第一个参数是指向类D对象本身的引用(this),第二个参数是'int'。不,不完全是这样。最终,这是一个接受参数值和'this'值的函数,但不一定按照这个顺序。非静态成员函数可能会使用与非成员函数不同的调用约定。因此,这不一定与将'this'作为第一个参数调用常规函数相同,因为'this'可能会在不同的寄存器或堆栈插槽中传递,而不是通常传递第一个参数的位置。” - Steve Jessop
1
@Mike Seymour:Bind并不会调用函数,它只是将其转变为一个boost函数对象。任何符合参数签名arg0 > D&和arg1> int的函数都可以调用它。这就是我所说的魔法。 - Vishnu Kanwar
显示剩余5条评论
11个回答

20

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

Evil.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,如果我没记错?)将this作为最后一个参数传递。

因此,如果您想将成员函数视为具有额外this的普通函数,则必须限制自己使用特定的ABI。 本帖子提供了如何执行此操作的示例(或者更确切地说是为什么您真的不应该这样做的示例!)

  

那么,是否有一种方法(或黑客)可以使用作为函数参数传递类对象来调用D :: foo,而无需在类对象上使用.或->或.*或-> *运算符?

一个特定于平台的令人恶心的肮脏技巧......


看起来你是直接从二进制文件中使用nm提取混淆的符号名称。这不是我们要找的。 - Vishnu Kanwar
1
同意,这个问题不清楚,而且这个答案在批评它(除了第二个调用,在其中没有int值...) - aaronps
2
非常赞同指出,“this”作为一个不可见的第一个参数是实现细节,而不是标准规定。同时还提供了一个可行的解决方案。 - Tamás Szelei
@ams:我也觉得这是我能在这里得到的最好的。 - Vishnu Kanwar
2
@TristanBrindle:我已经将我的悬赏奖励授予这个回答,因为它理解了问题并给出了足够的思考(主要是编辑部分 :))。我还要感谢您花费的时间和努力来撰写这个答案。 - Vishnu Kanwar
显示剩余2条评论

14

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

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

bind 并没有进行任何“魔法”操作;在其实现的某个地方,它确实是这样做的。

归根结底,它是一个接受两个参数的函数。

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


4

我认为唯一的方法是用一个类替换你的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;
 }

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

2

是的,C++11标准中有一个称为INVOKE的抽象概念,适用于可调用对象。指向成员函数的指针(PTMF)对象是可调用的,而this作为第一个(普通)参数被传递。

虽然已经提出了std::invoke函数,但目前尚不存在。要从任何可调用对象获取仿函数,可以使用std::bind,它是Boost.bind的派生类。

这很好地工作:

int main ( void ) 
{
    D d;

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

http://ideone.com/VPWV5U


请问您能否再详细解释一下? - Vishnu Kanwar
@VishnuKanwar 是哪部分?它是一个标准设施,与您所示的 Boost Bind 类似,但是新的 auto 关键字使声明易于使用,并创建本地对象。 - Potatoswatter

2
但是必须有一种方法来避免c++所施加的这种人为限制。
您所说的“人为限制”是什么意思?这只是语言定义的语法。有什么问题吗? "bind()"的“魔力”将在内部使用“-> *”运算符调用函数。

从技术角度看,“f(D,5)”是通过指向成员函数的指针调用的合理语法,尽管语言定义走了不同的方向,这种方式也可以被视为一种人工的方法。 - Pete Becker
@PeteBecker 我更倾向于遵循Steve Jessop评论中的观点,即它可能并不完全等同。在C程序员的意义上,“人工”的意义可能是如此。 - πάντα ῥεῖ
当考虑不存在的事物的属性时,您必须非常小心。语言可能没有固有的理由来提供具有确切和明显含义的语法,在这种情况下,它将是完全等效的。 - Pete Becker

1
是的,如果足够扭曲,您可以规避“新”的成员函数语法,但我必须问:为什么呢?该语法促进了理解成员函数是如何从周围对象调用的。鉴于虚拟函数的存在,这实际上(并且必须)是这种情况。它还自动更改了this指针的调用虚拟函数(以及对虚拟继承的支持)。

要了解如何执行此操作,请查看FastDelegate库的实现。其实现需要知道编译器成员函数指针的二进制结构(其中可能有几种类型)。那就是您要找的“hack”。 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指针。它还找到底层函数的实际地址,并记录这两个值以供以后使用。当调用发生时,它通过使用“-> *”进行一些诡计来强制编译器“做正确的事情”。但是,如果您真的想避免甚至依赖“-> *”运算符,那么此时,您可以进行一些类型转换,并将指针转换为“__thiscall”函数指针(嗯,假设您在Windows上):

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

那么,你到底不喜欢 someInstance->someFunction() 的哪一点呢?


那么,你到底不喜欢 someInstance->someFunction() 的哪一点呢?从某种意义上说是的。如果你想重新阅读我的问题,我已经让它更加清晰了。谢谢。 - Vishnu Kanwar
我仍然不明白你为什么要这样做。你是想从C代码中调用一些C++库吗?如果是的话,那就别再试了,使用一个导出C可调用函数的C++包装器即可。 - Speed8ump

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

在这里,->* 用于 return (obj->*mem)(args...); - Vishnu Kanwar
啊,我以为你只是想要语法糖。 - nishantjr

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) {}

To:

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

谢谢。但我不是在寻找一个可用的程序。请重新阅读问题陈述。 - Vishnu Kanwar

0
有没有一种方法可以在左侧没有类对象的情况下调用类的成员函数?
简而言之,不能。如果没有在调用时指定对象,则无法调用非静态类成员函数。对于静态函数,如果未指定类,则编译器无法知道你要调用哪个函数。这是因为它位于类或对象内部,超出了当前范围。有办法“黑科技”代码,使得你可以像示例中的main()一样书写。其他答案给出了这些黑科技的示例。
------------使用静态函数-----------------
一个类中的函数可以在不指定类对象的情况下在类外被调用。方法是创建一个函数指针的typedef,其签名与类函数相匹配,然后创建这样一个指针并将其赋值为类函数的地址。这个函数可以被调用而不需要在左侧指定类或对象。
该函数必须是静态的。ISO C++不允许取绑定成员函数的地址以形成指向该函数的指针,即不能从类对象创建指向非静态成员函数的指针。您可能能够找到一些非ISO标准兼容的编译器,但我没有任何关于此的指导。
对于非静态类成员函数,参数“this”是一个隐含的参数,并且始终是一个隐含的参数。您无法手动指定它,但是可以通过传递指向该类对象的指针来模拟它。
因此,对于您的代码示例:
  • 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,我重新阅读了你的问题,并在我的回答中进行了补充以回答它。 - Jed Schaaf
静态成员函数无论如何都是独立于类对象的。但这并不是我在寻找的,抱歉。 - Vishnu Kanwar
好的。就像我在评论中所说的那样,ISO C++不允许指向非静态对象函数的指针。你必须找到一个不符合ISO规范的编译器。(或者自己编写一个!) - Jed Schaaf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接