派生 class 的原始类型数据成员是否可以用作其基本构造函数的参数?
Can a primitive-type data member of a derived class be used as a parameter to its base constructor?
受我(目前已删除)对 的回答(但我的评论中有一个摘要)的启发,我想知道 Derived
class 的构造函数是否在下面的代码表现出未定义的行为。
#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;
}
我知道,在调用base c'tor 时,还没有(正式)构造派生对象,所以variable
成员的生命周期还没有开始。但是,this excerpt from the C++ Standard (thanks to NathanOliver 供参考)建议(也许)该对象可以“以有限的方式”使用(加粗我的):
7 Similarly, before the lifetime of an object has started
but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before
the storage which the object occupied is reused or released, any
glvalue that refers to the original object may be used but only in
limited ways. For an object under construction or destruction, see
[class.cdtor]. Otherwise, such a glvalue refers to allocated storage
([basic.stc.dynamic.allocation]), and using the properties of the
glvalue that do not depend on its value is well-defined. …
显然,如果 variable
是一个本身具有非平凡构造函数的对象,那么(几乎可以肯定)这里会有未定义的行为。但是,对于像 int
这样的原始(或 POD)类型,我们可以假设它的存储已经分配了吗?而且,如果是这样,上面引述的最后一句话是否成立,或者这仍然是 UB?
顺便说一句,无论是 clang-cl 还是 MSVC,即使启用了完整的警告,也不会对显示的代码进行任何诊断,并且它会按预期运行。但是,我很欣赏这些工具都不符合 C++ 标准的正式 interpretation/enforcement。
行为未定义,无论 Base
构造函数是否通过引用接受参数。
当控制到达Base(variable = 50)
时,variable
的生命周期还没有开始,因为数据成员是在基类之后初始化的。
所以首先,写入它会导致 UB,因为生命周期尚未开始。然后,因为你是按值传递的,从里面读也是UB。
[class.base.init]/13
In a non-delegating constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class ..., virtual base classes are initialized ...
— Then, direct base classes are initialized ...
— Then, non-static data members are initialized in the order they were declared in the class definition ...
— Finally, the ... the constructor body is executed.
@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;
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
而 GCC 和 MSVC 接受它。
受我(目前已删除)对 Derived
class 的构造函数是否在下面的代码表现出未定义的行为。
#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;
}
我知道,在调用base c'tor 时,还没有(正式)构造派生对象,所以variable
成员的生命周期还没有开始。但是,this excerpt from the C++ Standard (thanks to NathanOliver 供参考)建议(也许)该对象可以“以有限的方式”使用(加粗我的):
7 Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see [class.cdtor]. Otherwise, such a glvalue refers to allocated storage ([basic.stc.dynamic.allocation]), and using the properties of the glvalue that do not depend on its value is well-defined. …
显然,如果 variable
是一个本身具有非平凡构造函数的对象,那么(几乎可以肯定)这里会有未定义的行为。但是,对于像 int
这样的原始(或 POD)类型,我们可以假设它的存储已经分配了吗?而且,如果是这样,上面引述的最后一句话是否成立,或者这仍然是 UB?
顺便说一句,无论是 clang-cl 还是 MSVC,即使启用了完整的警告,也不会对显示的代码进行任何诊断,并且它会按预期运行。但是,我很欣赏这些工具都不符合 C++ 标准的正式 interpretation/enforcement。
行为未定义,无论 Base
构造函数是否通过引用接受参数。
当控制到达Base(variable = 50)
时,variable
的生命周期还没有开始,因为数据成员是在基类之后初始化的。
所以首先,写入它会导致 UB,因为生命周期尚未开始。然后,因为你是按值传递的,从里面读也是UB。
[class.base.init]/13
In a non-delegating constructor, initialization proceeds in the following order:
— First, and only for the constructor of the most derived class ..., virtual base classes are initialized ...
— Then, direct base classes are initialized ...
— Then, non-static data members are initialized in the order they were declared in the class definition ...
— Finally, the ... the constructor body is executed.
@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;
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
而 GCC 和 MSVC 接受它。