【问题标题】:Is a pointer to a virtual member function valid in the constructor of the base class?指向虚拟成员函数的指针在基类的构造函数中是否有效?
【发布时间】:2011-03-06 18:51:39
【问题描述】:

我的问题不是关于从基类构造函数调用虚成员函数,而是关于虚成员函数的指针在基类构造函数中是否有效。

鉴于以下

class A
{
    void (A::*m_pMember)();

public:
    A() :
        m_pMember(&A::vmember)
    {
    }

    virtual void vmember()
    {
        printf("In A::vmember()\n");
    }

    void test()
    {
        (this->*m_pMember)();
    }
};

class B : public A
{
public:
    virtual void vmember()
    {
        printf("In B::vmember()\n");
    }
};

int main()
{
    B b;
    b.test();

    return 0;
}

这会为所有兼容的 c++ 编译器生成“In B::vmember()”吗?

【问题讨论】:

  • 我猜不会。在 A 的构造函数内部,*this 是 A 类的对象,而 vtable 是 A 的 vtable。此外,如果&A::vmember 导致指向B::vmember 的指针,我会感到惊讶。但话又说回来,我不是 C++ 大师。
  • @Péter Török:是的,但这就是 vtable 的重点,不是吗。位置保持不变,但子类将其函数指针填充到基类的 vtable 中。因此,函数指针是正确的,但在构造函数中调用它却不是。注意函数指针是在构造函数完成后调用的
  • 嗯...可能会。不过,我粗略的理解是,&A::vmember 的结果不是指向 vtable 的指针,而是指向具体成员函数的指针。
  • @Francesco:您可以使用地球上所有可能的编译器进行测试,如果标准中没有明确规定,您仍然无法得出“它有效”的结论。
  • @ereOn:标准中的定义扎实而清晰。

标签: c++ member-function-pointers


【解决方案1】:

我认为没有。指向虚拟成员函数的指针是通过 VMT 解析的,因此与调用此函数的方式相同。这意味着它是无效的,因为 VMT 是在构造函数完成后填充的。

【讨论】:

  • 请注意,该问题仅明确询问指针是否有效,而不是在构造函数中调用它。在构造函数完成并且 vtable 完成后调用函数指针
  • 但是什么是无效的调用指针。它要么是有效的——那么你可以调用它,或者它是无效的并且你不能调用它。无论如何 - 为什么需要一个无效且不会被调用的指针?
  • 它将被调用,只是不会从构造函数中调用。 m_pMember 是从示例中的 test() 函数调用的
  • @Andrey:完全不正确。从构造函数调用虚函数没有任何限制,无论是通过指针还是直接调用。唯一要记住的是对象的动态类型是其构造函数当前正在工作的类型。
  • @AndreyT "从构造函数调用虚拟函数" 有,我会试着找到
【解决方案2】:

阅读this article,深入讨论成员函数指针以及如何使用它们。这应该可以回答您的所有问题。

【讨论】:

  • 此外,这是一篇旧文章 - 例如,它涉及 MSVC4-7。这个问题明确地与兼容编译器有关。
  • 当要求的内容是简单的是或否答案时,需要阅读大量文本。那么它是什么?
  • 这在很大程度上取决于你的继承图,但通常你会从对象的开头得到一个有效的 offset,而不是完整的指针。
  • 文章可能是旧的,但为了解决问题,它针对旧的编译器。获取代码并亲自查看。如果没有教育意义,这很有趣。
【解决方案3】:

IMO 是 implementation defined 获取虚函数的地址。这是因为虚函数是使用特定于编译器实现的 vtables 实现的。由于在类 ctor 的执行完成之前不能保证 vtable 是完整的,因此指向此类表(虚拟函数)中的条目的指针可能是 implementation defined behavior

几个月前我在 SO here 上提出了一个相关的问题;这基本上说在 C++ 标准中没有指定虚函数的地址。

因此,无论如何,即使它适合您,该解决方案也不会是可移植的。

【讨论】:

  • -1,不。除非标准明确要求实现来记录其行为,否则没有实现定义。也不能是未指明的行为,因为没有合理的行为范围。
  • @MSalters:好的,那么您是否将处理虚函数定义为未定义行为?
  • @Abhay:不正确。即使通过指针调用虚函数,也没有定义任何实现。
  • @AndreyT:请澄清“没有定义任何关于虚函数的实现”。我认为它们的实现方式(vtables)本身是实现定义的。
  • @Abhay:实现的细节总是“定义的实现”,这里没有参数 :) 我的意思是虚函数的 行为 和指向它们的指针C++ 语言是实现定义的。至于这种行为是如何实现的——根本不重要。
【解决方案4】:

“有效”是应用于指针时的特定术语。数据指针在指向对象或NULL时有效;函数指针指向函数或NULL时有效,成员指针指向成员或NULL时有效。

但是,根据您关于实际输出的问题,我可以推断您想问其他问题。让我们看看你的 vmember 函数 - 还是我应该说函数?显然有两个功能体。您可以只将派生的一个设为虚拟,这样也可以确认确实有两个 vmember 函数,它们恰好都是虚拟的。

现在,问题变成了在获取成员函数的地址时是否已经选择了实际函数。您的实现表明它们没有,并且这只发生在实际取消引用指针时。

必须以这种方式工作的原因是微不足道的。获取成员函数的地址并不涉及实际的对象,这是解决虚拟调用所需要的。让我告诉你:

namespace {
  void (A::*test)() = &A::vmember;
  A a;
  B b;
  (a.*test)();
  (b.*test)();
}

当我们初始化test时,根本没有AB类型的对象,但是否可以取&A::vmember的地址。然后可以将相同的成员指针用于两个不同的对象。除了 "In A::vmember()\n" 和 "In B::vmember()\n" 之外还能产生什么?

【讨论】:

  • 我发现 b.A::vmember()(b.*&A::vmember)() 没有产生相同的效果真的很令人困惑......
  • 应该是(a.*test)()b 也一样。
  • 令人费解的差异是两个语义上并不相似的表达式之间的句法相似性的结果。特别是,第一个不使用指向虚拟成员函数的指针。因此,它的行为并没有解决标题中的问题。
【解决方案5】:

我在Old New Thing(Raymond Chen 的博客,有时被称为微软的 Chuck Norris)上找到了一点解释。

当然,它没有说明合规性,但它解释了原因:

B b;

b.A::vmember(); // [1]

(b.*&A::vmember)(); // [2]

1 和 2 实际上调用了一个不同的函数……这真的很令人惊讶。这也意味着您实际上不能使用指向成员函数的指针来阻止运行时调度:/

【讨论】:

  • 你的意思可能是通过指针调用成员函数时不能禁用运行时调度。
  • 我的意思是“你实际上不能使用指向成员函数的指针调用非虚拟函数”对我来说听起来有点误导。
【解决方案6】:

指针是有效的,但是你必须记住,当通过指针调用虚函数时,它总是根据左侧使用的对象的 dynamic 类型进行解析-手边。这意味着当您从构造函数调用虚函数时,无论您是直接调用它还是通过指针调用它都没有关系。在这两种情况下,调用都将解析为其构造函数当前正在工作的类型。这就是虚函数的工作原理,当您在对象构造(或销毁)期间调用它们时。

还要注意,指向成员函数的指针通常不会在初始化时附加到特定函数。如果目标函数是非虚拟的,他们可以说指针指向一个特定的函数。但是,如果目标函数是虚函数,则无法说明指针指向的位置。例如,语言规范明确规定,当您比较(是否相等)恰好指向虚函数的两个指针时,结果是未指定

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-08
    • 1970-01-01
    相关资源
    最近更新 更多