【问题标题】:How does std::array constructor initialize its array?std::array 构造函数如何初始化其数组?
【发布时间】:2022-01-22 08:51:21
【问题描述】:

我试图了解 std::array 构造函数是如何工作的,以及他如何获取一个数组并将其初始化为它的数组。

我正在搜索标准库文件,我找到了这段代码

#if _HAS_CXX17
template <class _First, class... _Rest>
struct _Enforce_same {
    static_assert(conjunction_v<is_same<_First, _Rest>...>,
        "N4687 26.3.7.2 [array.cons]/2: "
        "Requires: (is_same_v<T, U> && ...) is true. Otherwise the program is ill-formed.");
    using type = _First;
};

template <class _First, class... _Rest>
array(_First, _Rest...) -> array<typename _Enforce_same<_First, _Rest...>::type, 1 + sizeof...(_Rest)>;
#endif // _HAS_CXX17

这是构造函数吗?它是如何工作的?

谢谢!

【问题讨论】:

  • "我试图了解 std::array 构造函数的工作原理" 它没有构造函数;这就是它的工作原理。你是在问这段代码在做什么?
  • 你看的是推导指南,是用来做CTAD的,不是构造函数。
  • std::array 是一个聚合类型,这意味着它没有用户提供的构造函数
  • 哦,这令人困惑,它没有构造函数以及如何将给定数组初始化为其数组,例如:std::array nums = {1, 2, 3};

标签: c++ arrays


【解决方案1】:

正如上面 cmets 中已经提到的,std::array 是一种聚合类型,因此您不是在调用构造函数,而是在实际初始化数据成员。

您在问题中指向的代码允许创建 std::array 而无需说明数组的类型和大小。使用演绎指南完成此操作,如下面的代码所示。

如果你自己实现它,它的外观如下:

template<typename T, std::size_t SIZE>
struct MyArray {
    T arr[SIZE];    
};

// MyArray deduction guides:
// similar code was added to std::array in C++17 to allow the
// creation of a2 below, without providing template arguments
template <class _First, class... _Rest>
MyArray(_First, _Rest...) -> MyArray<_First, 1 + sizeof...(_Rest)>;

int main() {
    MyArray<int, 5> a1 = {1, 2, 3}; // since C++11
    MyArray a2 {1, 2, 3}; // deduced to MyArray<int, 3>, since C++17
    // creation of a2 is based on the deduction guides above
}

上面的代码忽略了向MyArray发送不同类型的情况,像这样:

MyArray a2 {1, 2.0, 3}; // still deduced to MyArray<int, 3> with our code

有几个选项可以处理上述问题:确定数组的类型基于第一个值的类型(正如我们在幼稚的实现中实际上所做的那样),确定类型将基于 @987654321 @ 的值,或者不允许这样的初始化。 C++ 标准决定禁止它,因此需要检查所有类型是否相同,如果不是则引发编译错误,例如使用static_assert。正如发布的原始代码中所做的那样,使用 _Enforce_same 结构。


内部数据成员的初始化基于aggregate initialization,发送到结构的值被传递到其字段中,如下例所示:

struct A {
    int a;
};

struct B {
    int a, b;
};

struct C {
    int a[5];
};

int main {
    A a = {1}; // goes to a.a
    B b = {1, 2}; // goes to b.a and b.b
    C c = {1, 2, 2}; // goes into the array c.a    
}

【讨论】:

  • 哦,好吧,我现在明白了,我仍然很困惑 MyArray arr 的值如何具有 {1, 2, 3} 以及构造函数的外观(如果定义了我)。跨度>
  • @ZakiMkn 添加到有关聚合初始化的答案说明中。至于构造函数的外观,所有的想法是它不需要构造函数,所以这更像是hypothetical question
  • 哦,好吧,顺便说一句,为什么当数组是私有数据成员时我不能进行聚合初始化?
  • @ZakiMkn 如果类(或结构)具有私有或受保护的数据成员,则它不是聚合类型,因此不能使用聚合初始化。
【解决方案2】:

@Amir Kirsh 的回答非常详细。所以我不想重复。相反,我邀请您查看以下代码的汇编输出

#include <iostream>
#include <array>

int main( )
{
    // std::array<int, 5> arr = { 1, 2, 3 }; // std::array
    int arr[5] { 1, 2 , 3 }; // raw array (aka C-style array)

    for ( const auto& i : arr )
    {
        std::cout << i << '\n';
    }

    return 0;
}

您可以检查和比较两个数组的汇编代码 here
只需取消注释std::array 版本并注释掉原始数组版本即可查看std::array 版本的汇编代码。

但为了让您的工作更轻松,以下是两个数组的代码:
std::array

        mov     rax, QWORD PTR .LC0[rip]
        mov     QWORD PTR [rsp+28], 0
        lea     rbx, [rsp+16]
        lea     rbp, [rsp+36]
        mov     QWORD PTR [rsp+16], rax
        mov     DWORD PTR [rsp+24], 3

raw array

        mov     rax, QWORD PTR .LC0[rip]
        mov     QWORD PTR [rsp+28], 0
        lea     rbx, [rsp+16]
        lea     rbp, [rsp+36]
        mov     QWORD PTR [rsp+16], rax
        mov     DWORD PTR [rsp+24], 3

是的!可以看出,它们是相同的。 std::array 在编译期间被转换成其等效的C 样式数组。这只是语法糖。实际上不需要构造函数析构函数。两者是相同的东西并且存储在堆栈中。它们构造简单,这意味着它们可以通过递增堆栈指针寄存器和一些其他快速指令来构造。

就像这样:

#include <iostream>
#include <type_traits>
#include <array>

int main( )
{
    std::cout << std::boolalpha << "Is std::array<int, 5> trivially constructible: "
              << std::is_trivially_constructible< std::array<int, 5> >::value << '\n';

    std::cout << std::boolalpha << "Is int[5] trivially constructible: "
              << std::is_trivially_constructible< int[5] >::value << '\n';

结果:

Is std::array<int, 5> trivially constructible: true
Is int[5] trivially constructible: true

【讨论】:

  • 您的回答让事情变得更加清晰,谢谢。但是我有点缺乏关于 c++ 编译器的知识,每个 c++ 程序都必须转换成汇编语言吗?机器码是从汇编还是 c++ 代码创建的?
  • @ZakiMkn 通常,编译器会将 CPP 代码转换为包含机器代码的目标文件。在这种情况下不需要组装。汇编语言是为人类服务的。计算机无法读取汇编代码,只能读取二进制机器码。
猜你喜欢
  • 2014-02-27
  • 2016-02-16
  • 2015-09-04
  • 1970-01-01
  • 1970-01-01
  • 2019-04-28
  • 2013-10-14
  • 1970-01-01
  • 2015-01-02
相关资源
最近更新 更多