为什么C ++没有指向成员函数类型的指针?

时间:2009-01-20 17:08:52

标签: c++ function pointers member

我在这里完全错了,但据我所知,C ++并没有真正的“成员函数指针”类型。我知道你可以用Boost和mem_fun等做技巧。但是为什么C ++的设计者决定不使用包含指向函数的指针的64位指针和指向对象的指针?例如?

我的意思是指向未知类型特定对象的成员函数的指针。 I.E.您可以用于回调的内容。这将是一个包含两个值的类型。第一个值是指向函数的指针,第二个值是指向对象的特定实例的指针。

我不是指一个指向类的一般成员函数的指针。 E.G。

int (Fred::*)(char,float)

它本来是如此有用,让我的生活更轻松。

雨果

9 个答案:

答案 0 :(得分:17)

@RocketMagnet - 这是对你的other question的回复,是被标记为重复的{{3}}。我正在回答那个问题,而不是这个问题。

通常,C ++指向成员函数的指针不能在类层次结构中进行转换。那说你经常可以逃脱它。例如:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};

int main() {
    typedef void (A::*pmf_t)();
    C c; c.x = 42; c.y = -1;

    pmf_t mf = static_cast<pmf_t>(&C::foo);
    (c.*mf)();
}

编译此代码,编译器正确抱怨:

$ cl /EHsc /Zi /nologo pmf.cpp
pmf.cpp
pmf.cpp(15) : warning C4407: cast between different pointer to member representations, compiler may generate incorrect code

$

所以回答“为什么C ++没有指向成员函数的指针 - 函数在void-class?”是这个想象中的基类 - 所有东西都没有成员,所以你没有安全分配给它的价值! “void(C :: )()”和“void()()”是互不兼容的类型。

现在,我打赌你在考虑“等等,我之前已经投出了成员函数指针!”是的,您可以使用reinterpret_cast和单继承。这与其他重新解释演员阵容属于同一类别:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};
class D { public: int z; };

int main() {
    C c; c.x = 42; c.y = -1;

    // this will print -1
    D& d = reinterpret_cast<D&>(c);
    cout << "d.z == " << d.z << "\n";
}

因此,如果void (void::*)()确实存在,但没有任何东西可以安全/可移植地分配给它。

传统上,你在使用void (*)(void*)的任何地方都使用了签名void (void::*)()的函数,因为虽然成员函数指针在继承层次上不能很好地投射,但void指针却演得很好。代替:

#include <iostream>
using std::cout;
class A { public: int x; };
class B { public: int y; };
class C : public B, public A { public: void foo(){ cout << "a.x == " << x << "\n";}};

void do_foo(void* ptrToC){
    C* c = static_cast<C*>(ptrToC);
    c->foo();
}

int main() {
    typedef void (*pf_t)(void*);
    C c; c.x = 42; c.y = -1;

    pf_t f = do_foo;
    f(&c);
}

那么你的问题。为什么C ++不支持这种类型的转换。指向成员函数类型的指针已经必须处理虚拟和非虚拟基类,以及虚拟和非虚拟成员函数,它们都在同一类型中,在某些平台上将它们扩展为4 * sizeof(void *)。我认为因为它会使指针到成员函数的实现更加复杂,并且原始函数指针已经很好地解决了这个问题。

正如其他人所评论的那样,C ++为图书馆编写者提供了足够的工具来完成这项工作,然后像你我这样的“普通”程序员应该使用这些库而不是冒出这些细节。

编辑:标记社区维基。请仅编辑以包含对C ++标准的相关引用,并添加斜体。 (尤其是在我理解错误的地方添加对标准的引用!^ _ ^)

答案 1 :(得分:12)

正如其他人所指出的,C ++确实有一个成员函数指针类型。

您正在寻找的术语是“绑定功能”。 C ++没有为函数绑定提供语法糖的原因是因为它的哲学只提供了最基本的工具,然后你可以用它来构建你想要的所有工具。这有助于保持语言“小”(或至少,令人难以置信的巨大)。

类似地,C ++没有像C#那样的lock {}原语,但它有一个RAII,由boost的scoped_lock使用。

当然,有一种思想流派认为你应该为可能有用的一切添加语法糖。无论好坏,C ++都不属于那所学校。

答案 2 :(得分:4)

我认为你所寻找的可能就是这些图书馆......

快速代表http://www.codeproject.com/KB/cpp/FastDelegate.aspx

Boost.Function http://www.boost.org/doc/libs/1_37_0/doc/html/function.html

以下是函数指针相关问题的完整解释http://www.parashift.com/c++-faq-lite/pointers-to-members.html

答案 3 :(得分:3)

It does.

例如,

int (Fred::*)(char,float)

是指向类Fred的成员函数的指针,该成员返回int并获取charfloat

答案 4 :(得分:2)

我认为答案是C ++的设计者选择不在语言中拥有可以在库中轻松实现的东西。您自己对所需内容的描述为实现它提供了一种完全合理的方式。

我知道这听起来很有趣,但C ++是一种简约语言。他们确实留给了他们可以留给他们的所有图书馆。

答案 5 :(得分:1)

TR1具有std :: tr1 :: function,它将被添加到C ++ 0x中。所以在某种意义上它确实拥有它。

C ++的设计理念之一是:你不为你不使用的东西买单。 C#style delagates的问题是它们很重并且需要语言支持,每个人都会为他们是否使用它们付费。这就是首选库实现的原因。

委托很重要的原因是方法指针通常比普通指针大。只要方法是虚拟的,就会发生这种情方法指针将根据基类使用它来调用不同的函数。这需要至少两个指针,即vtable和偏移量。该方法涉及的其他奇怪之处在于涉及多重继承的类。

所有这一切,我不是编译器作家。有可能为绑定方法指针创建一个新类型,它会破坏引用的方法的虚拟性(毕竟我们知道绑定方法时基类是什么)。

答案 6 :(得分:1)

问题肯定不是在一个易于使用的包中有一个对象指针和一个函数指针的基础知识,因为你可以使用指针和thunk来做到这一点。 (VC86在x86上已经使用了这样的thunk以支持指向虚拟成员函数的指针,因此这些指针只占用4个字节。)你可能最终得到很多thunk,这是真的,但人们已经依赖于链接器了消除重复的模板实例化 - 无论如何我都这么做 - 并且只有这么多的vtable和这种抵消你最终会在实践中结束。对于任何合理大小的程序来说,开销可能并不重要,如果你不使用这些东西,那么它不会花费你任何东西。

(传统上使用TOC的架构会将TOC指针存储在函数指针部分中,而不是存储在thunk中,就像它们必须已经存在一样。)

(当然,这种新类型的对象不能完全替代正常的函数指针,因为大小会有所不同。但是,它们在调用点会被写成相同的。)

我看到的问题是调用约定的问题:在一般情况下支持指向函数的指针可能很棘手,因为生成的代码必须以相同的方式准备参数(包括这些),而不考虑指针指向的实际类型的事物,函数或成员函数。

这对x86来说可能不是什么大问题,至少不是这个问题,因为你可以只加载ECX并接受如果调用函数不需要它那么它将是假的。 (我认为VC ++假设ECX在这种情况下仍然是假的。)但是在将寄存器中的命名参数的参数传递给函数的体系结构中,最终可能会在thunk中进行相当多的重排,并且如果向左推送堆栈参数到了那时你基本上被塞满了。这不能静态修复,因为在极限情况下没有交叉翻译单位信息。

[编辑:MSalters,在上面对rocketmagnet帖子的评论中指出,如果对象和函数都已知,那么可以立即确定此偏移量等等。这完全没有发生在我身上!但是,考虑到这一点,我想只需要存储确切的对象指针,可能是偏移量,以及确切的函数指针。这使得thunk完全不必要 - 我认为 - 但我很确定指向成员函数和非成员函数的问题都将保留。]

答案 7 :(得分:0)

C ++已经是一门大语言,添加它会使它更大。你真正想要的甚至比一个绑定的成员函数更糟糕,它更接近于boost :: function。您希望同时存储void(*)()和一对回调。毕竟,原因是你希望给调用者一个完整的回调,而被调用者不应该关心确切的细节。

大小可能是sizeof(void*)+sizeof(void(*)())。指向成员函数的指针可能更大,但这是因为它们是未绑定的。例如,他们需要处理你“获取虚拟功能地址”的可能性。但是,内置的绑定指针到成员函数类型不会受到此开销的影响。它可以解析绑定时要调用的确切函数。

使用UDT无法做到这一点。 boost :: function在绑定对象指针时不能丢弃PTMF的开销。您需要了解PTMF,vtable等的结构 - 所有非标准的东西。但是,我们现在可以使用C ++ 1x实现目标。一旦它在std ::中,它对于编译器供应商来说是公平的游戏。标准库实现本身不可移植(参见例如type_info)。

我想,即使编译器供应商在库中实现了这一点,你仍然希望有一些很好的语法。我想std::function<void(*)()> foo = &myX && X::bar。 (它不会与现有语法冲突,因为X :: bar不是表达式 - 只有&amp; X :: bar是)

答案 8 :(得分:-1)

我发现方法有一个隐式this参数,因此指向方法的c指针不足以允许调用该方法(因为无法确定哪个实例应该用于this(或者即使任何实例当前存在))。

编辑: Rocketmagnet评论说他在问题中解决了这个问题,而且看起来似乎是这种情况,但我认为这是在我开始此回复后添加的。但无论如何我会说“mea culpa”。

所以请允许我稍微扩展一下这个想法。

C ++与c密切相关,并且其所有内在类型都与早期语言兼容(主要是因为c++开发的历史,我想)。因此,内在c++指针是c指针,并且无法支持您要求的使用。

当然你可以构建一个派生类型来完成这项工作 - 就像在boost实现中一样 - 但是这样的生物属于一个库。