【问题标题】:C++ interview inheritance puzzle [duplicate]C ++面试继承难题[重复]
【发布时间】:2012-01-08 08:30:47
【问题描述】:

可能重复:
C++ virtual function from constructor
Calling virtual functions inside constructors

这个问题是面试时问的。

我想我已经正确回答了第一部分,但不确定第二部分。事实上,我对第二部分一无所知。

  1. 以下代码生成什么输出?为什么?
  2. 如果将 A::Foo() 设为纯虚函数,它会生成什么输出?

当我尝试使用 virtual void foo() = 0; 在我的编译器上运行相同的问题时,它会抛出 错误“未定义对 `A::Foo()' 的引用”

#include <iostream>

using namespace std;

class A     
{    
public:       
    A()             
    {
        this->Foo();
    }
    virtual void Foo() 
    {
        cout << "A::Foo()" << endl;
    }
};

class B : public A      
{     
public:     
    B()      
    {
        this->Foo();      
    }
    virtual void Foo() 
    {
        cout << "B::Foo()" << endl;
    }
};

int main(int, char**)
{
    B   objectB;
    return 0;
}

【问题讨论】:

  • 那么...你现在有什么问题?
  • 1.第一个问题的答案是:A::Foo() B::Foo() 2. sec 问题的答案是:它取决于如果您将 A 类更改为:class A { public: A() { this ->Foo(); } 虚拟 void Foo() = 0 { cout Foo(); 会出现编译错误} 虚空 Foo() = 0; };

标签: c++ inheritance


【解决方案1】:

构造函数中的方法作为类的动态类型进行调度。 A 的构造函数使用动态类型 A 调用 Foo。(有关正确定义,请参阅 AlfP.Steinbach 的注释)

如果 A 是抽象基,那么错误是因为它试图调用纯虚方法。

A()             
{
    this->Foo(); // call A::Foo
}

来自 Scott Meyers 的 Effective C++:

这种看似违反直觉的行为是有充分理由的。因为基类构造函数在派生类构造函数之前执行,所以在基类构造函数运行时派生类数据成员尚未初始化。如果在基类构造过程中调用的虚函数下降到派生类,派生类函数几乎肯定会引用本地数据成员,但这些数据成员还没有被初始化。这将是未定义行为和深夜调试会话的不间断票。调用对象中尚未初始化的部分本质上是危险的,因此 C++ 没有办法做到这一点。

【讨论】:

  • ahhh 所以你的意思是如果我把我的代码改成这样的 public: A() { this->Foo(); } 虚空 Foo() =0; };它会抛出错误???
  • -1 “构造函数不执行虚拟调用——它们调用自己的方法”是不正确的。
  • @AlfP.Steinbach 你能详细说明一下吗?
  • @Pubby:任何调用的效果都不会根据调用是否在构造函数中发出而改变。调用虚成员函数的效果取决于调用该函数的对象的动态类型。在类 T 构造函数中,正在构造的对象的动态类型是 T。
  • +1 我也有同样的想法,并认为它适用于这里。此外,如果类型始终是正在创建的对象的类型,那么最终结果就是他所说的那样。
【解决方案2】:

在第二种情况下,您有未定义的行为(在类 T 构造函数中调用类 T 的纯虚),因此输出可以是任何东西——如果它甚至可以编译的话。

主要要了解的是,在C++中,当一个对象的T构造函数执行时,对象的动态类型是T。

这使得从 C++ 构造函数调用虚函数变得安全。您不会调用未初始化的派生类子对象。相比之下,在 Java 和 C#(以及类似语言)中,您很容易遇到这种错误,而且很常见。

【讨论】:

    【解决方案3】:

    当您实例化 B 对象时,会发生以下情况:

    1. B的构造函数被调用。

    2. 首先,B 的构造函数调用基础构造函数 A()

    3. A 的构造函数中,函数调用被调度到A::foo(),因为this 具有静态动态类型A*(如果你考虑一下);现在A 子对象已完成。

    4. 现在B 的构造函数主体运行。这里函数调用被分派到B::foo()。现在整个B 对象就完成了。

    如果A::foo() 是纯虚拟的,步骤(3) 会导致未定义的行为;参看。 10.6/4 在标准中。

    (在您的情况下可能表现为链接器错误,因为编译器会优化以静态解析调用,并且找不到符号 A::foo。)

    【讨论】:

    • 1. “静态调度”的解释有点误导。这不能保证。如果编译器可以证明将执行哪个实现,则它可以进行优化,但它可以针对任何虚拟调用进行这种优化,而不仅仅是来自构造函数的调用。
    • 纯虚拟案例是错误的。不会有链接器错误,而是未定义的行为,在这种特殊情况下(实现质量),我知道的所有编译器都会打印一条消息并调用terminate()。静态调度...不一定是静态调度,它可以使用动态调度,但重要的是正在构造的对象的类型是A,而A构造函数正在执行(考虑构造函数可以调用将分派给虚函数的非虚函数,将使用动态分派,但仍将调用A::foo()
    • @AlfP.Steinbach:你是什么意思?在基本构造函数中,this 具有静态 动态类型 A*。啊,好吧,你的意思是它可能仍然动态发送到A::foo()...好吧,我会澄清一下。
    • 如果A::foo() 是纯虚拟的,那么步骤 (3) 不一定会给出链接器错误 - 我遇到过编译器会在定义函数时调用该函数,而其他编译器在运行时会失败-time 如果没有定义。您只能说从构造函数或析构函数调用纯虚函数会产生未定义的行为。
    • @MikeSeymour:是的,已经修复了。 (不过,仍在等待参考。)
    【解决方案4】:

    将调用构造函数,以便首先构造父对象,这样就不会对未定义的对象有任何依赖。因此它是 A::A 后跟 B::B。编辑:也有可能 B 的构造函数直接调用 A 的,如 Kerrek SB says - 最终效果是一样的。

    在第一种情况下,输出将是“A::Foo()”,然后是“B::Foo()”。在 A 的构造时,B 还不存在,它的虚函数还不是对象的一部分。

    在第二种情况下,您将调用纯虚函数 A::Foo,这将产生错误或完全拒绝编译。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-08
      • 1970-01-01
      • 2015-07-30
      • 2012-02-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多