Adr*_*ica 15 c++ constructor undefined-behavior language-lawyer
受到我(目前已删除)对这个问题的回答的启发(但我的评论中有一个摘要),我想知道Derived
下面代码中类的构造函数是否表现出未定义的行为。
#include <iostream>
class Base {
public:
Base(int test) {
std::cout << "Base constructor, test: " << test << std::endl;
}
};
class Derived : public Base {
private:
int variable;
public: Derived() : Base(variable = 50) { // Is this undefined behaviour?
}
};
int main()
{
Derived derived;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我知道,当基 c'tor 被调用时,派生对象尚未(正式)构造,因此variable
成员的生命周期尚未开始。但是,C++ 标准的摘录(感谢NathanOliver提供参考)表明(可能)该对象可以“以有限的方式”使用(粗体我的):
7 同样,在对象的生命周期开始之前 但在对象将占用的存储空间已经分配之后,或者在对象的生命周期结束之后并且在对象占用的存储空间被重用或释放之前,任何引用可以使用原始对象,但只能以有限的方式使用。对于正在构建或销毁的对象,请参阅 [class.cdtor]。否则,这种泛左值指的是分配的存储([basic.stc.dynamic.allocation]),并且使用不依赖于其值的泛左值的属性是明确定义的。…
显然,如果variable
一个对象本身有一个非平凡的构造函数,那么(几乎可以肯定)这里会有未定义的行为。但是,对于像 an 这样的原始(或 POD)类型int
,我们是否可以假设已经为其分配了存储空间?而且,如果是这样,上面引用的最后一句话是否成立,或者这仍然是 UB?
顺便说一句,即使启用了完整警告,clang-cl 和 MSVC 都不会对显示的代码进行任何诊断,并且它会按预期运行。但是,我很欣赏这些工具都没有资格作为 C++ 标准的正式解释/执行。
Hol*_*Cat 13
无论Base
构造函数是否通过引用接受参数,行为都是未定义的。
当控制到达 时Base(variable = 50)
, 的生命周期variable
还没有开始,因为数据成员是在基类之后初始化的。
所以首先,写入它会导致 UB,因为生命周期尚未开始。然后,因为您按值传递,因此从中读取也是 UB。
[class.base.init]/13
在非委托构造函数中,初始化按以下顺序进行:
— 首先,并且仅针对最派生类的构造函数...,初始化虚拟基类...
— 然后,直接基类被初始化...
— 然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化......
— 最后,...构造函数体被执行。
@Jarod42 的想法:作为一个实验,你可以在一个constexpr
应该抓住 UB的上下文中尝试这个。
#include <type_traits>
#include <iostream>
struct Base
{
int x;
constexpr Base(int x) : x(x) {}
};
struct Derived : Base
{
int variable;
constexpr Derived() : Base(variable = 42) {}
};
constexpr Derived derived;
Run Code Online (Sandbox Code Playgroud)
Clang 拒绝了这一点:
error: constexpr variable 'derived' must be initialized by a constant expression
note: assignment to object outside its lifetime is not allowed in a constant expression
Run Code Online (Sandbox Code Playgroud)
而 GCC 和 MSVC 接受它。