这是invoking the copy constructor within the constructor的衍生产品。
我相信一个对象是完全形成的,并且可以预期在初始化列表的末尾表现如此(编辑:我错了!)。具体来说,成员函数和从构造函数本身访问本地状态的行为与其他任何成员函数完全相同。
这似乎是一个有点争议的观点,但另一种选择是,只有构造函数正常返回后才能完全形成对象。
以下是快速&用于此的脏测试用例,显示初始化初始化列表中提到的所有成员字段以及未进行默认构建的成员字段。
#include <cstdio>
struct 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吗?
答案 0 :(得分:5)
是否在初始化列表的末尾完全构造了一个对象?
不,不是。对象this
在构造函数执行结束时完全构造。
但是,所有成员都是在初始化列表的末尾构造的。
差异很微妙但很重要,因为它与析构函数的执行有关。如果this
对象在执行构造函数期间抛出异常,则会破坏每个构造的成员和基类。 this
对象的析构函数只有在完全构造后才会执行。
- 对于类或聚合类型的任何对象,如果它或其任何子对象由除了普通的默认构造函数之外的任何东西初始化,则生命周期在初始化结束时开始。
- 对于析构函数不重要的类类型的任何对象,生命周期在析构函数的执行开始时结束。
答案 1 :(得分:5)
您的示例是明确定义的行为,但仅限于此。
要清楚,我们看到的"invoke dtor"
行来自您的异常的破坏,而不是顶级i
对象。
类的每个成员都是在构造函数体启动时初始化的,尽管在构造函数体完成之前,对象本身并未初始化。这就是为什么不在顶级调用对象上调用~invoke
的原因。
throw *this
表达式从invoke
复制初始化*this
对象,允许 。 (该标准明确指出“构造函数[...]可以在构造或销毁期间调用”。)您的复制初始化是默认的,它只是复制初始化所有成员 - 这些成员都已初始化。
然后因为你的构造函数体正在通过异常退出,所有初始化的成员都被破坏,异常被传播,捕获,然后被处理掉,调用~invoke
,并反过来摧毁这些成员。