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
的(唯一)对象的完整对象
本质上,您引用的句子在代码中使doingstatic_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(){ }
}