提问人:Ben 提问时间:3/25/2021 更新时间:3/25/2021 访问量:303
可选未初始化的类:std::is_trivially_constructible 对于非默认构造函数似乎不正确?
Optionally-uninitialized class: std::is_trivially_constructible seems incorrect for non-default constructor?
问:
我觉得没有告诉我真相。一、语境:std::is_trivially_constructible<T, Arg>
我有一个小向量类,.默认构造它默认构造它的成员,所以 .这是所需的行为。VectorND<T, N>
VectorND<float, 2>{} == VectorND<float, 2>{float{}, float{}} == VectorND<float, 2>{0.f, 0.f}
但是,有时,对于性能关键代码,我想在未初始化的情况下构造它们。我的想法是,我可以使用这样的标签类型:
struct uninitalized_t {};
static constexpr uninitalized_t uninitalized;
...
VectorND(uninitalized_t) {}
...
VectorND<float, 2> x{uninitalized}; //< Tell x to be uninitialized.
我可以让它起作用。首先,如果我这样做
template <typename T, std::size_t N>
class VectorND {
std::array<T, 2> x;
public:
VectorND() = default;
T& operator[](std::size_t i) { return x[i]; }
};
https://godbolt.org/z/oTvo33xEb
然后,默认构造函数使数据保持未初始化状态。与 if 相同。VectorND() {}
如果我做到了,那么数据就会归零,使其不再像预期的那样简单地构造。这是所需的默认行为。VectorND() : x{} {}
但是,如果我添加 ,那么看起来确实是未初始化的: https://godbolt.org/z/j9KWhPT7n 这又是我想要的。但出于某种原因,是.为什么?我试过了explicit VectorND(uninitalized_t) {}
VectorND<float, 2> x{uninitialized}
std::is_trivially_constructible_v<VectorND<float, 2>, uninitalized_t>
false
VectorND() = default; // Or VectorND() {};
explicit VectorND(uninitalized_t) {}
和
VectorND() = default; // Or VectorND() {};
explicit VectorND(uninitalized_t) : VectorND() {}
我仍然得到.https://godbolt.org/z/3n1Mz9dWdstd::is_trivially_constructible_v<VectorND<float, 2>, uninitalized_t> == false
为什么?
答:
简答题
这是行不通的,因为这不是标准定义简单构造函数的方式。
琐碎与初始化(或缺乏初始化)无关,它只是标准提出的一组要求,用于将构造函数定义为琐碎的——一种状态,它使编译器能够更好地生成代码,并由库作者进行优化。
长答案
就 C++ 标准而言,琐碎的状态与成员是否初始化无关。琐碎的副产品是,您可能会从正在进行琐碎值初始化的对象中获取未初始化的数据,但这并不意味着未初始化的数据是琐碎的定义。
从形式上讲,该标准只是概述了非常具体的构造函数被视为微不足道的标准:
默认构造函数由 class.default.ctor/3 定义
如果默认构造函数不是用户提供的,并且如果:
- 它的类没有虚函数 ([class.virtual]) 也没有虚拟基类 ([class.mi]),并且
- 其类的非静态数据成员没有默认成员初始值设定项 ([class.mem]),并且
- 其类的所有直接基类都具有简单的默认构造函数,并且
- 对于其类中属于类类型(或其数组)的所有非静态数据成员,每个此类类都有一个简单的默认构造函数。
否则,默认构造函数是非平凡的。
Copy/Move 构造函数由 class.copy.ctor/11 定义
类 X 的复制/移动构造函数不是用户提供的,并且如果:
- 类 X 没有虚函数 ([class.virtual]) 和虚拟基类 ([class.mi]),并且
- 选择用于复制/移动每个直接基类子对象的构造函数是微不足道的,并且
- 对于类类型(或其数组)的 X 的每个非静态数据成员,选择复制/移动该成员的构造函数是微不足道的;
否则,复制/移动构造函数是非平凡的。
注意:对析构函数和析构函数也有类似的要求operator=
但是,对于不是复制、移动或默认构造函数的自定义构造函数,没有任何实际定义是微不足道的。这意味着任何自定义构造函数都不能被视为微不足道的。
这也有效地意味着,只有当它测试默认构造函数、复制构造函数或移动构造函数时,才能计算 to。std::is_trivially_constructible<T,Args...>::value
true
为什么琐碎很重要?
之所以存在“微不足道”的状态,是因为它为编译器和库作者提供了更好的优化保证。用于复制/移动的简单构造函数相当于简单的指令,只需复制数据(而不是需要任何清理或重新布线
对象的构造函数通常可能需要)。mov
此外,如果一个类型满足足够多的简单要求,它可能是“简单可复制的”——这允许编译器和库作者简单地复制数据,而不需要构造函数调用。这也可用于在不违反严格别名的情况下查看不同的对象表示形式。std::memcpy
评论
std::is_trivially_constructible<T,Args...>::value
template <typename T> concept is_uninitializable = std::is_trivial_v<T> || std::is_constructible_v<T, uninitalized_t>;
评论
VectorND x = {};
VectorND x;
= default
VectorND() {}
= default