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

Is there a way to call member function without .* or ->* operator

本文关键字:运算符 情况下 函数 成员 调用 gt 有没有      更新时间:2023-10-16

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

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

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

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

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

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

那么,有没有办法(或hack(使用作为函数参数传递给它的类对象来调用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)) ();

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

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

邪恶:

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

邪恶:

#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作为最后一个参数。

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

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

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

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

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

bind没有做任何"魔术";在它的实现中,它正是这样做的。

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

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

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

你在寻找这样的东西吗?

在下面的代码中,我使用宏来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;
 }

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

两种方式无需组装

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

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

"人为限制">是什么意思?这只是语言定义的语法。怎么了?bind()的"魔力"将在内部使用 ->* 运算符来调用函数。

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

是的,确实,只要有足够的扭曲,您就可以逃避"新"成员函数语法,但我不得不问:您为什么要这样做? 该语法有助于理解成员函数以某种方式从周围对象调用。 鉴于虚函数的存在,这实际上是(并且必须(的情况。 它还会自动更改调用虚拟函数(以及对虚拟继承的支持(所需的 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 所需的指针。 它还查找基础函数的实际地址,并记录这两个值供以后使用。 当调用发生时,它会通过使用->*的一些技巧迫使编译器"做正确的事情"。 但是,如果你真的想避免依赖->*运算符,你可以在这一点上做一些类型转换并将指针变成"__thiscall"函数指针(好吧,假设你在Windows上(:

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

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

在这个问题上,标准异常清晰,完全明确。第5.2.2(C++11(节第一句规定:

函数调用

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

就是这样 - 它们是不同的东西,你不能把它们混为一谈。

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

#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 . 或 ->,以下方法将起作用

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

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

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

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

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

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

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

static类成员函数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);

虽然我认为这是一种奇怪的事情,但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;
}                                                                
相关文章: