11

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

我的具体意思是指向未知类型的特定对象的成员函数的指针。IE 可以用于回调的东西。这将是一个包含两个值的类型。第一个值是指向函数的指针,第二个值是指向对象特定实例的指针。

我的意思不是指向类的一般成员函数的指针。例如

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

它会非常有用,让我的生活更轻松。

雨果

4

8 回答 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-class 上?” 是这个虚构的基类没有成员,所以没有任何值可以安全地分配给它!“void (C:: )()”和“void (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++ 标准的相关引用,并添加斜体。(特别是在我的理解错误的地方添加对标准的引用!^_^)

于 2009-01-20T19:29:43.203 回答
12

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

您正在寻找的术语是“绑定函数”。C++ 没有为函数绑定提供语法糖的原因是因为它的理念是只提供最基本的工具,然后您可以使用这些工具构建您想要的一切。这有助于保持语言“小”(或者至少,减少令人难以置信的巨大)。

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

当然,有学派认为你应该为所有可能有用的东西添加语法糖。无论好坏,C++ 都不属于那个学校。

于 2009-01-20T17:18:50.863 回答
3

确实如此。

例如,

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

是指向Fred返回 anint并接受 achar和 a的类的成员函数的指针float

于 2009-01-20T17:11:43.763 回答
2

我认为答案是 C++ 的设计者选择不在语言中包含可以在库中轻松实现的东西。你自己对你想要什么的描述给出了一种完全合理的实现方式。

我知道这听起来很有趣,但 C++ 是一种极简语言。他们确实把所有能留给他们的东西都留给了图书馆。

于 2009-01-22T14:40:06.550 回答
1

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

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

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

尽管如此,我不是编译器作者。可能已经有可能为绑定方法指针创建一个新类型,这将颠覆所引用方法的虚拟性(毕竟我们知道如果方法被绑定,基类是什么)。

于 2009-01-20T18:41:18.077 回答
1

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

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

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

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

这在 x86 上可能没什么大不了的,至少对于 thiscall 来说不是,因为您可以不顾一切地加载 ECX 并接受如果调用函数不需要它,那么它将是假的。(而且我认为 VC++ 在这种情况下无论如何都假定 ECX 是伪造的。)但是在将寄存器中命名参数的参数传递给函数的架构上,您最终可能会在 thunk 中进行大量改组,并且如果堆栈参数被推向左侧- 向右,那么你基本上已经吃饱了。这不能静态修复,因为在限制中没有交叉翻译单元信息。

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

于 2009-01-21T01:35:40.207 回答
0

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

大小可能是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 是)

于 2009-01-22T14:22:28.613 回答
-1

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

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

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

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

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

于 2009-01-20T18:57:23.567 回答