这是在构造函数中调用复制构造函数的衍生产品.
我相信一个对象是完全形成的,并且可以预期在初始化列表的末尾表现如此(编辑:我错了!).具体来说,成员函数和从构造函数本身访问本地状态的行为与其他任何成员函数完全相同.
这似乎是一个有点争议的观点,但另一种选择是,只有构造函数正常返回后才能完全形成对象.
以下是一个快速和脏的测试用例,它显示初始化初始化列表中提到的所有成员字段以及未获取默认构造的成员字段.
#includestruct noise { noise() { printf("noise default constructed\n"); } noise(int x) { printf("noise integer constructed %u\n", x); } ~noise() { printf("noise dtor\n"); } }; struct invoke : public noise { noise init; noise body; invoke() : noise(3), init(4) { body = noise(5); throw *this; // try to use the object before returning normally } ~invoke() { printf("invoke dtor\n"); } }; int main() { try { invoke i; } catch (...) { } }
这打印至少在我的机器上,
noise integer constructed 3 noise integer constructed 4 noise default constructed noise integer constructed 5 noise dtor noise dtor noise dtor noise dtor invoke dtor noise dtor noise dtor noise dtor
与往常一样,很难区分work-as-specified和works-as-my-compiler-implemented!这实际上是UB吗?
对象是否完全构建在初始化列表的末尾?
不它不是.该对象this
在构造函数执行结束时完全构造.
但是,所有成员都是在初始化列表的末尾构造的.
差异很微妙但很重要,因为它与析构函数的执行有关.如果this
对象在执行构造函数期间抛出异常,则会破坏每个构造的成员和基类.this
对象的析构函数只有在完全构造后才会执行.
从cppreference:
对于类或聚合类型的任何对象,如果它或其任何子对象由除了普通的默认构造函数之外的任何东西初始化,则生命周期在初始化结束时开始.
对于析构函数不是微不足道的类类型的任何对象,生命周期在析构函数的执行开始时结束.
您的示例是明确定义的行为,但仅限于此.
要清楚,"invoke dtor"
我们看到的那条线来自你的异常的破坏,而不是顶级i
对象.
类的每个成员在构造函数体启动时初始化,尽管在构造函数体完成之前,对象本身未初始化.这就是为什么~invoke
不调用顶级调用对象.
的throw *this
表达复制初始化一个invoke
从对象*this
,这是允许的.(标准明确指出"构造函数[...]可以在构造或销毁期间调用".)您的复制初始化是默认的,它只是复制初始化所有成员 - 这些成员都已初始化.
然后因为你的构造函数体正在通过异常退出,所有初始化的成员都被破坏,异常被传播,捕获,然后被处理,调用~invoke
,并且反过来也会破坏这些成员.