【问题标题】:Why doesn't C++ have a pointer to member function type?为什么 C++ 没有指向成员函数类型的指针?
【发布时间】:2010-10-02 12:12:06
【问题描述】:

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

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

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

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

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

雨果

【问题讨论】:

标签: c++ function pointers member


【解决方案1】:

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

一般来说,指向成员函数的 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++ 标准的相关引用,并添加斜体。 (特别是在我的理解错误的地方添加对标准的引用!^_^)

【讨论】:

  • 谢谢。这个回答最有道理。正如我所看到的,阻止它的一个问题是需要其他类能够重载该回调函数。也许如果不允许重载回调,它是可能的......
  • Rocket - 你仍然可以超载它。考虑 [[ class C { public: virtual void foo(); }; ]]。从 C 派生的类仍然可以使用 'do_foo' 作为它们的函数指针值。 [[ D d;某事(do_foo,static_cast(&d)); ]]
  • 指向成员的指针需要很大,正是因为它们是未绑定的。一旦你将它绑定到一个对象,你就有了对象 vtable(s)。
【解决方案2】:

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

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

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

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

【讨论】:

    【解决方案3】:

    It does.

    例如,

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

    是指向类Fred 的成员函数的指针,它返回int 并接受char 和float。

    【讨论】:

    • 那只是一个指向函数的指针,不是吗?所以这不是雨果的意思。他的意思类似于 .NET 中的委托类型。
    • 我猜他的意思是指向任何类的成员函数的指针(可能是 NO 类的成员),只要隐式“this”指针以外的参数具有相同的类型。
    • @ZachHirsch 这是一个成员函数 pointer 类型。有成员函数类型吗? (顺便还有函数和函数指针类型。)
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

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

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

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

      【讨论】:

        【解决方案6】:

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

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

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

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

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

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

        【讨论】:

        • 感谢您的帮助。回复:最后 2 段,假设参数总是相同的,这应该不是问题。例如。指针的类型将被声明为接受 int 并返回 int 的成员函数指针。任何具有 int foo(int) 成员函数的对象都可以传递该 ptr。
        • 是的,如果你限制你的代表总是引用绑定的成员函数,那没问题。不过,让我感到震惊的是,能够同时支持成员函数和非成员函数会更好。请注意,也许这只是过度概括。我没有考虑具体的用例。
        【解决方案7】:

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

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

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

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

        【讨论】:

          【解决方案8】:

          我突然想到方法有一个隐含的this 参数,所以指向方法的c 指针不足以允许调用该方法(因为没有办法确定应该为@ 使用哪个实例987654323@(或者即使 any 实例当前存在))。

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

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

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

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

          【讨论】:

          • RTQ - 我在问题中解决了这个问题。
          • C++ 确实有(未绑定的)指向成员函数的指针,这与 C 中的任何东西都不同。绑定留给(标准)库类型。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-03-24
          • 1970-01-01
          • 1970-01-01
          • 2017-07-27
          • 2013-06-06
          相关资源
          最近更新 更多