Optional-uninitialized class: std::is_trivially_constructible 对于非默认构造函数似乎不正确?

Optionally-uninitialized class: std::is_trivially_constructible seems incorrect for non-default constructor?

我觉得 std::is_trivially_constructible<T, Arg> 没有告诉我真相。一、上下文:

我有一个小向量class,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

然后默认构造函数使数据未初始化。与 VectorND() {}.

相同

如果我做到了 VectorND() : x{} {},那么数据将归零,使其不再像预期的那样容易构造。这是所需的默认行为。

但如果我添加 explicit VectorND(uninitalized_t) {},那么 VectorND<float, 2> x{uninitialized} 似乎未初始化:https://godbolt.org/z/j9KWhPT7n 这又是我想要的。但由于某种原因,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() {}

我仍然得到 std::is_trivially_constructible_v<VectorND<float, 2>, uninitalized_t> == falsehttps://godbolt.org/z/3n1Mz9dWd

为什么?

简答

这是行不通的,因为这不是标准定义普通构造函数的方式。

琐碎与初始化(或缺少初始化)无关,它只是标准规定的一组要求,用于将构造函数定义为 琐碎——a编译器生成更好的代码并库作者进行优化的状态。

长答案

微不足道的状态,就C++标准而言,与是否成员初始化无关。琐碎的 副产品 是您可能会从进行琐碎值初始化的对象中获取未初始化的数据——但这并不意味着未初始化的数据就是琐碎的定义。

从形式上讲,该标准只是概述了非常具体的构造函数被认为是微不足道的标准:

  • 默认构造函数由 class.default.ctor/3

    定义

    A default constructor is trivial if it is not user-provided and if:

    • its class has no virtual functions ([class.virtual]) and no virtual base classes ([class.mi]), and
    • no non-static data member of its class has a default member initializer ([class.mem]), and
    • all the direct base classes of its class have trivial default constructors, and
    • for all the non-static data members of its class that are of class type (or array thereof), each such class has a trivial default constructor.

    Otherwise, the default constructor is non-trivial.

  • Copy/Move 构造函数由 class.copy.ctor/11

    定义

    A copy/move constructor for class X is trivial if it is not user-provided and if:

    • class X has no virtual functions ([class.virtual]) and no virtual base classes ([class.mi]), and
    • the constructor selected to copy/move each direct base class subobject is trivial, and
    • for each non-static data member of X that is of class type (or array thereof), the constructor selected to copy/move that member is trivial;

    otherwise the copy/move constructor is non-trivial.

注:operator=和析构函数

也有类似的要求

但是,对于不是复制、移动或默认构造函数的自定义构造函数,没有任何实际定义是微不足道的。这意味着任何自定义构造函数不能被认为是微不足道的。

这也有效地意味着如果 std::is_trivially_constructible<T,Args...>::value 测试默认构造函数、复制构造函数或移动构造函数,则它只能计算为 true

为什么微不足道很重要?

存在“微不足道”的状态是因为它为编译器和库作者提供了更好的优化保证。 copy/moving 的简单构造函数相当于简单的 mov 指令来复制数据(而不是要求任何清理或重新布线 对象的构造函数通常可能需要)。

此外,如果一个类型满足足够多的简单要求,它可能是“可简单复制”的——这允许编译器和库作者简单地复制 [=14]周围的数据=] 而不是要求构造函数调用。这也可用于在不违反严格别名的情况下查看不同的对象表示。