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

11

我可能完全错误,但据我所知,C++并没有原生的“成员函数指针”类型。我知道您可以使用Boost和mem_fun等技巧来实现。但是,为什么C++的设计者决定不采用包含指向函数和对象指针的64位指针,例如?

我具体指的是指向未知类型的特定对象的成员函数的指针。也就是说,您可以将其用作回调的类型,其中包含两个值。第一个值是指向函数的指针,第二个值是指向对象的特定实例的指针。

我不是指类的一般成员函数的指针,例如:

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

它本来会很有用并且让我的生活更轻松。

Hugo


2
C++确实有成员函数指针。请查看下面的链接。http://www.parashift.com/c++-faq-lite/pointers-to-members.html - chappar
我认为你可能在这些库中找到你要找的东西... Fast Delegates 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 - Damien
不,如果你仔细阅读问题,你会发现我在谈论一个函数指针,它包含指向函数的指针和*指向对象的指针。这是我可以用作回调的东西。 - Rocketmagnet
我确实完全阅读了问题。 - Iraimbilanja
请注意,C++11确实拥有这样一个指针:std::function,可以通过std::bind获得。 - Kuba hasn't forgotten Monica
8个回答

17

@RocketMagnet - 这是对你的其他问题的回答,也就是那个被标记为重复的问题。我回答的是那个问题,而不是这个。

一般来说,在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 (C::)()" 和 "void (void::)()" 是相互不兼容的类型。
现在,我敢打赌你正在想 "等等,我之前已经成功地转换过成员函数指针了!" 是的,你可能使用reinterpret_cast和单一继承来做到了这一点。这与其他reinterpret转换属于同一类别:
#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++给库编写者提供了足够的工具来完成这项工作,然后像你和我这样的'普通'程序员应该使用这些库,而不是费力地处理这些细节。

编辑:标记为社区wiki。请只添加与C++标准相关的参考,并将其用斜体字显示。(尤其是在我的理解有误时添加标准引用! ^_^ )


谢谢。这个回复最有道理。我认为,防止它的唯一问题是其他类需要能够重载该回调函数。也许如果禁止重载回调函数,那么这将成为可能... - Rocketmagnet
火箭 - 你仍然可以超载它。考虑[[ class C { public: virtual void foo(); }; ]]。从C派生的类仍然可以使用“do_foo”作为它们的函数指针值。[[ D d; something(do_foo, static_cast<C*>(&d)); ]] - Aaron
指向成员的指针需要很大,正是因为它们是未绑定的。一旦将其绑定到对象上,您就拥有了对象的虚表。 - MSalters

12

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

你要找的术语是“绑定函数”。C++不提供函数绑定的语法糖的原因是因为它的哲学是只提供最基本的工具,然后你可以用这些工具构建你想要的一切。这有助于保持语言的“小”(或者至少不会让人感到非常复杂)。

同样,C++没有像C#那样的lock{}原语,但它有RAII,这是由boost的scoped_lock使用的。

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


3

它确实可以。

例如,

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

这是一个指向类Fred的成员函数的指针,该函数返回一个int类型的值,并且需要一个char类型和一个float类型的参数。


那只是指向函数的指针,不是Hugo所指的内容。他的意思类似于.NET中的委托类型。 - OregonGhost
我猜他的意思是一个指向任何类的成员函数的指针(可能是没有类的成员),只要除了隐式的“this”指针之外的参数具有相同的类型。 - isekaijin
@ZachHirsch 这是一个成员函数指针类型。是否有成员函数类型?(顺便说一下,还有函数和函数指针类型。) - KeyC0de

2
我认为答案是,C++的设计者选择不在语言中包含可以很容易地在库中实现的东西。你对所需内容的描述提供了一个完全合理的实现方式。
我知道听起来有点滑稽,但C++是一种极简主义语言。他们把所有能留给库的都留给了它们。

1
TR1有std::tr1::function,它将被添加到C++0x中。因此从某种意义上说,它确实拥有它。
C++的设计哲学之一是:你不为你不使用的东西付费。 C#风格的委托的问题在于它们很重,并且需要语言支持,无论是否使用它们,每个人都要为此付费。这就是为什么库实现更受欢迎的原因。
委托重的原因在于方法指针通常比普通指针大。每当方法是虚拟的时,就会发生这种情况。方法指针将调用不同的函数,具体取决于使用它的基类是什么。这至少需要两个指针,即vtable和偏移量。如果该方法来自涉及多重继承的类,则还涉及其他奇怪的问题。
尽管如此,我并不是编译器编写者。可能可以创建一个新类型的绑定方法指针,以颠覆所引用方法的虚拟性(毕竟,如果方法被绑定,我们知道基类是什么)。

1
问题肯定不是拥有一个对象指针和函数指针的基础知识,因为你可以使用指针和thunk来实现这一点。(这样的thunk已经被VC++在x86上用于支持指向虚拟成员函数的指针,以便这些指针只占用4个字节。)你可能最终会得到很多thunks,没错,但人们已经依赖链接器来消除重复的模板实例化——无论如何我都这么做——而且在实践中你最终只会得到那么多vtable和this偏移量。对于任何合理大小的程序,开销可能不会很大,如果你不使用这些东西,它也不会花费你任何东西。
(传统使用TOC的架构将在函数指针部分存储TOC指针,而不是在thunk中存储,就像他们已经做过的那样。)
(这种新类型的对象当然不能完全替代普通的函数指针,因为它们的大小不同。但是,在调用点处它们将被写成相同的方式。)
我看到的问题是调用约定:以这种方式支持函数指针可能在一般情况下会很棘手,因为生成的代码必须以相同的方式准备参数(包括此参数),而不考虑指针所指向的实际类型、函数或成员函数。

在x86上可能并不是什么大问题,至少不是使用thiscall时,因为你可以加载ECX并接受如果调用函数不需要它,则它将是虚假的。 (我认为VC ++在这种情况下假定ECX是虚假的。)但是在将参数传递给命名参数的寄存器的体系结构上,您可能会在thunk中遇到大量洗牌,如果堆栈参数从左到右推送,则基本上无法解决。 而且这不能在静态上修复,因为在极限情况下没有跨翻译单元信息。

[编辑:MSalters在rocketmagnet上面的评论中指出,如果同时知道对象和函数,则可以立即确定此偏移量等。我完全没有想到这一点!但是,考虑到这一点,我认为只需要存储确切的对象指针、可能的偏移量和确切的函数指针。这使得thunks完全不必要——我想——但我很确定指向成员函数和非成员函数的问题仍然存在。]


感谢您的有益回答。关于最后两段,假设参数始终相同,这不应该是一个问题。例如,指针类型将被声明为成员函数指针,该指针接受 int 类型的参数并返回 int 类型的值。任何具有 int foo(int) 成员函数的对象都可以传递该指针。 - Rocketmagnet
是的,如果您限制委托始终引用绑定成员函数,那就没有问题。但我认为最好能够支持成员函数和非成员函数。请注意,也许这只是过度概括了。我没有具体的用例想法。 - please delete me

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 不是表达式 - 只有 &X::bar 是)


-1

我想到方法有一个隐式的this参数,因此一个指向方法的c指针是不足够的,无法调用该方法(因为没有办法确定应该使用哪个实例作为this(甚至是否存在任何实例))。

编辑:Rocketmagnet评论说他在问题中解决了这个问题,看起来确实是这样,尽管我认为这是在我开始回答之后添加的。但我还是要说“我的错”。

所以让我稍微扩展一下这个想法。

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

当然,您可以构建一个派生类型来完成这项工作——就像boost实现中一样——但这样的东西属于库。


RTQ - 我在问题中提到了这个。 - Rocketmagnet
C++确实具有(未绑定的)成员函数指针,这与C中的任何内容都不同。绑定留给(标准)库类型。 - MSalters

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