11

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

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

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

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

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

  1. 类对象本身(指向 D 类对象的指针称为this
  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)) ();

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

4

11 回答 11

20

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

邪恶.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 是 for this)

虽然确实自 CFront 以来的每个编译器都这样做了,但这只是一个实现细节。C++ 标准竭力指定成员函数应该如何实现,而只是它们应该如何表现。

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

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

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

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

于 2013-11-01T07:36:15.820 回答
14

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

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

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

归根结底,它是一个带有两个参数的函数

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

于 2013-10-09T10:34:35.790 回答
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;
 }
于 2013-11-06T17:41:22.203 回答
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;
}
于 2013-11-06T18:15:55.253 回答
2

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

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

于 2013-10-09T10:35:17.763 回答
2

是的,C++11 标准中有一个称为INVOKE的抽象,它适用于 Callable 对象。Pointer-to-member-function (PTMF) 对象是 Callable 并this作为第一个(常规)参数传递。

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

这工作正常:

int main ( void ) 
{
    D d;

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

http://ideone.com/VPWV5U

于 2013-11-07T04:05:48.450 回答
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 和朋友们正在做的非常相似,但通常更快(尽管很明显,便携性较差)。

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

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

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

于 2013-11-02T00:47:36.877 回答
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/oa 。或->,以下将起作用

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;
}
于 2013-11-06T04:43:00.207 回答
0

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

...

那么,有没有办法(或hack)将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);
于 2013-11-02T00:39:16.030 回答
0

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

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

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

于 2013-11-04T08:02:43.133 回答
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;
}                                                                
于 2013-11-08T02:03:15.300 回答