C++ 使用对象表达式在构造函数内调用虚函数

C++ 使用对象表达式在构造函数内调用虚函数,c++,C++,代码: #包括 使用std::cout; 使用std::endl; 结构A { 虚拟void foo() { cout§1.8[介绍对象]/p2-3: 对象可以包含其他对象,称为子对象。子对象 可以是成员子对象(9.2)、基类子对象(子句) 10) ,或数组元素。不是任何 另一个对象称为完整对象 对于每个对象x,都有一个称为完整对象的对象 对于x,确定如下: 如果x是一个完整对象,则x是x的完整对象 否则,x的完整对象是包含x的(唯一)对象的完整对象 本质上,您引用的句子在代码中使doing

代码:

#包括
使用std::cout;
使用std::endl;
结构A
{
虚拟void foo()
{

cout§1.8[介绍对象]/p2-3:

对象可以包含其他对象,称为子对象。子对象 可以是成员子对象(9.2)、基类子对象(子句) 10) ,或数组元素。不是任何 另一个对象称为完整对象

对于每个对象
x
,都有一个称为完整对象的对象 对于
x
,确定如下:

  • 如果
    x
    是一个完整对象,则
    x
    x
    的完整对象
  • 否则,
    x
    的完整对象是包含
    x
    的(唯一)对象的完整对象
本质上,您引用的句子在代码中使doing
static_cast(this)->foo();
B
的构造函数中未定义行为,即使构建的完整对象是
C
。该标准实际上提供了一个很好的示例:

#include <iostream>

using std::cout;
using std::endl;

struct A
{
    virtual void foo()
    {
        cout << "A" << endl;
    }

    A(){ }
};

struct B : A
{
    B();
    virtual void foo()
    {
        cout << "B" << endl;
    }
};

B b;

B::B()
{
    b.foo();
    foo();
}  

struct C : B
{
    virtual void foo()
    {
        cout << "C" << endl;
    }

    C() : B(){ }      
};

C c;

int main(){ }

事实上,你已经可以:Ideone的编译器(GCC)实际上调用了
a->f();
行上的
V::f()
,即使指针指向的是一个完整构造的
a
子对象。

这有点棘手,我不得不多次编辑帖子(感谢那些帮助我的家伙),我将尽量使其简单,并参考N3690:

§12.7.4规定

构件函数,包括虚拟函数(10.3),可在构造或销毁过程中调用(12.6.2)

这就是你在B的构造函数中所做的

struct V {
    virtual void f();
    virtual void g();
};
struct A : virtual V {
    virtual void f();
};
struct B : virtual V {
    virtual void g();
    B(V*, A*);
};

struct D : A, B {
    virtual void f();
    virtual void g();
    D() : B((A*)this, this) { }
};

B::B(V* v, A* a) {
    f();    // calls V::f, not A::f
    g();    // calls B::g, not D::g
    v->g(); // v is base of B, the call is well-defined, calls B::g
    a->f(); // undefined behavior, a’s type not a base of B
}
目前这是完全合法的。This指针(在第二次函数调用中隐式使用)始终指向正在构造的对象

然后标准还说:

直接或间接地从构造函数和对象调用虚函数时 call applies是正在构造或销毁的对象(称为x),调用的函数是最终重写器 在构造函数或析构函数的类中,而不是在更派生的类中重写它(因此忽略函数的更派生版本)

因此,vtable并不像您所想的那样完全遍历,而是停止到构造函数的类版本的虚拟函数(请参阅)

仍然合法

最后谈谈你的观点:

如果虚拟函数调用使用显式类成员访问,例如。 (object.vfunction()或object->vfunction())且对象表达式引用x的完整对象或该对象的一个基类子对象,但不是x或其一个基类子对象(即,不是正在构造的对象或其一个基类子对象),行为未定义

要理解这句话,我们首先需要理解x的完整对象的含义:

§1.8.2

对象可以包含其他对象,称为子对象。子对象可以是成员子对象(9.2),即基对象 类子对象(第10条)或数组元素。不属于任何其他对象的子对象的对象是 称为完整对象

对于每个对象x,都有一个称为x的完整对象的对象,确定如下:

-如果x是一个完整的对象,那么x是x的完整对象

-否则,x的完整对象就是包含x的(唯一)对象的完整对象

如果你把上面的一段和前面的一段放在一起,你会发现你不能调用一个虚拟函数,它引用一个基类(即尚未构造的派生对象)的“完整类型”,或者一个拥有该成员或数组元素的对象

如果要在B的构造函数中显式引用C:

B::B()
{
    b.foo(); // virtual
    foo(); // virtual
}  
B::B(){
static_cast(this)->foo();//表示B的完整对象,即C
}
结构C:B
{
C():B(){}
}
那么你就会有未定义的行为

直观(或多或少)的原因是

  • 允许在构造函数中调用虚拟函数或成员函数,如果是虚拟函数,它将“停止虚拟层次结构遍历”到该对象,并调用其函数版本(请参阅)

  • 无论如何,如果你从一个子对象中引用该子对象的完整对象(重读标准段落),那么它是未定义的行为

经验法则:


如果我出了什么问题,请在下面的评论中告诉我,我会修改帖子。谢谢!

你想让B构造函数调用重写的C foo函数吗?你希望如何识别UB?结果可能是任何东西,包括伪装成非UB。我不理解这里的评论:
f();//调用V::f,而不是A::f
。为什么有人会想到这里的
A::f
?它从何而来?
A
不在继承中。看起来像是打字错误。
//调用B::g,而不是D::g
。好的。我知道了。B构造函数是从
D
构造函数调用的。在本例中,
B
I欢迎收看每日标准Q/A Dmitry Fucintv | T.C+1@quantdev标准也需要爱!@quantdev:D事实上,这对我来说也是一个很好的学习机会——我可能会阅读更多的标准来回答这些问题
B
的构造函数中很好。
这个
的类型毕竟是
B
B
的构造函数中。
这个
的类型在
B
的构造函数中是
的。当然,但是从C调用它并不好,我相信,在那种情况下类型应该是C不,它仍然是
B*
。构造函数中的
这个
的类型不会发生神奇的变化这取决于构造函数是如何调用的,当然不管你怎么做it@MarcoA.:是的,您不能调用virt
B::B() {
    static_cast<C*>(this)->foo(); // Refers to the complete object of B, i.e. C
}

struct C : B
{
    C() : B(){ }
}