简体   繁体   English

为什么 std::array 需要大小作为模板参数而不是构造函数参数?

[英]Why does std::array require the size as a template parameter and not as a constructor parameter?

There are many design issues I have found with this, particularly with passing std::array<> to functions.我发现了很多设计问题,尤其是将std::array<>传递给函数时。 Basically, when you initialize std::array, it takes in two template parameters, <class T and size_t size> .基本上,当您初始化 std::array 时,它接受两个模板参数, <class Tsize_t size> However, when you create a function that requires and std::array , we do not know the size, so we need to create template parameters for the functions also.但是,当您创建需要std::array的 function 时,我们不知道大小,因此我们还需要为函数创建模板参数。

template <size_t params_size> auto func(std::array<int, params_size> arr);

Why couldn't std::array take in the size at the constructor instead?为什么std::array不能在构造函数中取而代之? (ie): (IE):

auto array = std::array<int>(10);

Then the functions would look less aggressive and would not require template params, as such:然后函数看起来不那么激进并且不需要模板参数,如下所示:

auto func (std::array<int> arr);

I just want to know the design choice for std::array , and why it was designed this way.我只想知道std::array的设计选择,以及为什么这样设计。

This isn't a question due to a bug, but rather a question why std::array<> was designed in such a manner.这不是由于错误引起的问题,而是为什么std::array<>以这种方式设计的问题。

std::array<T,N> var is intended as a better replacement for C-style arrays T var[N] . std::array<T,N> var旨在更好地替代 C 风格的 arrays T var[N]

The memory space for this object is created locally , ie on the stack for local variables or inside the struct itself when defined as a member.这个 object 的 memory 空间是在本地创建的,即在局部变量的堆栈上或在定义为成员时在结构本身内部。

std::vector<T> in contrary always allocate it's element's memory in the heap.相反, std::vector<T>总是在堆中分配它的元素 memory。

Therefore as std::array is allocated locally, it cannot have a variable size since that space needs to be reserved at compile time.因此,由于std::array是在本地分配的,因此它不能具有可变大小,因为该空间需要在编译时保留。 std::vector in the other hand has the ability to reallocate and resize since its memory is unbounded.另一方面, std::vector具有重新分配和调整大小的能力,因为它的 memory 是无界的。

As a consequence, the big advantage of std::array in terms of performance is that it eliminates that one level of indirection that std::vector pays for its flexibility.因此, std::array在性能方面的一大优势在于它消除了std::vector为其灵活性付出的间接级别。

For example:例如:

#include <cstdint>
#include <iostream>
#include <vector>
#include <array>

int main() {
    int a;
    char b[10];
    std::vector<char> c(10);
    std::array<char,10> d;
    struct E {
        std::array<char,10> e1;
        std::vector<char> e2{10};
    };
    E e;

    printf( "Stack address:   %p\n", __builtin_frame_address(0));
    printf( "Address of a:    %p\n", &a );
    printf( "Address of b:    %p\n", b );
    printf( "Address of b[0]: %p\n", &b[0] );
    printf( "Address of c:    %p\n", &c );
    printf( "Address of c[0]: %p\n", &c[0] );
    printf( "Address of d:    %p\n", &d );
    printf( "Address of d[0]: %p\n", &d[0] );
    printf( "Address of e:    %p\n", &e );
    printf( "Address of e1:   %p\n", &e.e1 );
    printf( "Address of e1[0]:%p\n", &e.e1[0] );
    printf( "Address of e2:   %p\n", &e.e2);
    printf( "Address of e2[0]:%p\n", &e.e2[0] );
}

Produces产品

Program stdout
Stack address:   0x7fffeb115ed0
Address of a:    0x7fffeb115eb0
Address of b:    0x7fffeb115ea6
Address of b[0]: 0x7fffeb115ea6
Address of c:    0x7fffeb115e80
Address of c[0]: 0x1cad2b0
Address of d:    0x7fffeb115e76
Address of d[0]: 0x7fffeb115e76
Address of e:    0x7fffeb115e40
Address of e1:   0x7fffeb115e40
Address of e1[0]:0x7fffeb115e40
Address of e2:   0x7fffeb115e50
Address of e2[0]:0x1cad2d0

Godbolt: https://godbolt.org/z/75s47T56f Godbolt: https://godbolt.org/z/75s47T56f

Not an answer, really, because I used to despise std::array<> for the same reasons as you — anything with Monadic qualities are not good design (IMNSHO).不是答案,真的,因为我曾经出于与您相同的原因而鄙视std::array<> — 任何具有 Monadic 品质的东西都不是好的设计(恕我直言)。

Fortunately, C++20 has the solution: a dynamic std::span<> .幸运的是,C++20 有解决方案:动态std::span<>

#include <array>
#include <iostream>
#include <span>

namespace detail
{
  void print( const std::span<const int> & xs )
  {
    for (size_t n = 0;  n < xs.size();  n++)
      std::cout << xs[n] << " ";
  }
}

void print( const std::span<const int> & xs )
{
  std::cout << "{ ";
  detail::print( xs );
  std::cout << "}\n";
}

void add( const std::span<int> & xs, int n )
{
  for (int & x : xs)
    x += n;
}

int main()
{
  std::array<int,5> xs { 1, 2, 4, 6, 10 };
  add( xs, 1 );
  print( xs );
}

Notice that the span itself is const in all cases, but the elements themselves are modifiable unless they too are tagged const .请注意, span本身在所有情况下都是const ,但元素本身是可修改的,除非它们也被标记为const This is exactly what an array is like.这正是数组的样子。

std::span is a C++20 object. I know that MS and maybe others had a array_view in older versions of their libraries. std::span是 C++20 object。我知道 MS 和其他人可能在他们的库的旧版本中有一个array_view

tl;dr tl;博士
Use std::array only to declare your array object. Pass it around with a dynamic std::span .仅使用std::array来声明您的数组 object。使用动态std::span传递它。


std::array vs C array std::array 与 C 数组

The use-case for std::array is actually very narrow: encapsulate a fixed-size array as a first-class container object (one that can be copied, not just referenced). std::array的用例实际上非常狭窄:封装一个固定大小的数组作为一级容器 object(可以复制,而不仅仅是引用)。

At first blush this doesn't seem to be much of an improvement over standard C-style arrays:乍一看,这似乎与标准 C 风格 arrays 相比没有太大改进:

typedef int myarray[10];             // (1)
using myarray = std::array<int,10>;  // (2)

void f( myarray a );

But it is!但它是! The difference is in what f() actually gets:不同之处在于f()实际得到的是什么:

  1. For a C-style array, the argument is just a pointer — a reference to the caller's data (that you can modify.).对于 C 风格的数组,参数只是一个指针——对调用者数据(您可以修改)的引用。 You know the size of the referenced array ( 10 ), but writing code to get that size is not straight-forward even with the usual C array-size idiom ( sizeof(myarray)/sizeof(a[0]) , since sizeof(a) is the size of a pointer).您知道引用数组的大小 ( 10 ),但是即使使用通常的 C 数组大小惯用语 ( sizeof(myarray)/sizeof(a[0]) ,编写代码来获取该大小也不是直截了当的,因为sizeof(a)是指针的大小)。
  2. For the std::array , the argument value is an actual local copy of the caller's data.对于std::array ,参数值是调用者数据的实际本地副本 If you want to be able to modify the caller's data then you need to be explicit about declaring the formal argument as a reference type ( myarray & a ) or just to avoid an expensive copy ( const myarray & a ).如果您希望能够修改调用者的数据,那么您需要明确将形式参数声明为引用类型 ( myarray & a ) 或只是为了避免昂贵的副本 ( const myarray & a )。 This falls in line with how other C++ objects are passed.这与其他 C++ 对象的传递方式一致。 And though the size is still 10 , your code can query the size of the array with the usual C++ container idiom: a.size() !虽然大小仍然是10 ,但您的代码可以使用通常的 C++ 容器惯用语查询数组的大小: a.size()

The usual way C overcomes this is to clutter the call site and formal argument lists with information about the array size so that it doesn't get lost. C 克服这个问题的通常方法是用有关数组大小的信息使调用站点和正式参数列表混乱,这样它就不会丢失。

int f( int array[], size_t n )   // traditional C
{
  printf( "There are %zu elements.\n", n );
  recurse with f( array, n );
}

int main(void)
{
  int my_array[10];
  f( my_array, ARRAY_SIZE(my_array) );

The std::array way is cleaner. std::array方式更干净。

int f( std::array<int,10> & array )   // C++
{
  std::cout << "There are " << array.size() << " elements.\n";
  recurse with f( array );
}

int main()
{
  std::array<int,10> my_array;
  f( my_array );

But while cleaner, it is significantly less flexible than the C array, simply because its length is fixed .但是虽然更简洁,但它的灵活性明显不如 C 数组,原因很简单,因为它的长度是固定的。 A caller cannot pass a std::array<int,12> to the function, for example.例如,调用者不能将std::array<int,12>传递给 function。

I'll refer you to the other good answers here to consider more about container choice when handling arrayed data.我将在此处向您推荐其他好的答案,以便在处理阵列数据时更多地考虑容器选择。

If you have a problem with std::array and you think std::span is a solution, now you will have two problems.如果您对std::array有疑问并且您认为std::span是一个解决方案,那么现在您将遇到两个问题。

More seriously, without knowing what kind of conceptual operation is func it is difficult to tell what is the right alternative.更严重的是,如果不知道什么样的概念操作是有效的,就很难func什么是正确的选择。

First, if you want or can exploit to know the size at compile-time there is nothing cooler than what you are trying to avoid.首先,如果你想或可以利用在编译时知道大小,没有什么比你试图避免的更酷了。

template<std::size_t N> 
void func(std::array<int, N> arr);   // add & or && or const& if appropiate

Imagine it, knowing the size at compile time can allow you and the compiler to do all sorts of tricks, like unrolling loops completely or verifying logic at compile time (eg if you know the size must be smaller or bigger than a constant).想象一下,在编译时知道大小可以让您编译器执行各种技巧,例如完全展开循环或在编译时验证逻辑(例如,如果您知道大小必须小于或大于常量)。 Or the coolest trick of all, not needing to allocate memory for any auxiliary operation inside func (because you know the size of the problem a priori).或者最酷的技巧,不需要为func内的任何辅助操作分配 memory(因为你先验地知道问题的大小)。

If you want a dynamic array, use (and pass) a std::vector .如果你想要一个动态数组,使用(并传递)一个std::vector

void func(std::vector<int> dynarr);   // add & or && or const& if appropiate

But then you force your caller to use std::vector as the container.但是随后您强制调用者使用std::vector作为容器。

If you want a fixed array, and it will work with everything,如果你想要一个固定的数组,它适用于所有东西,

template<class FixedArray>
void func(FixedArray dynarr);   // add & or && or const& if appropiate

Ask yourself, how specific is your function such that you really really want to make it work with any size of std::array but not with std::vector ?问问自己,您的 function 有多具体,以至于您真的想让它与任何大小的std::array一起工作,但不能与std::vector一起工作? Why specifically int s even?为什么特别是int s even?

template<class ArithmeticRange>
void func(ArithmeticRange dynarr);   // add & or && or const& if appropiate

There are a few contiguous containers and ranges in C++ std . C++ std中有一些连续的容器和范围。 They serve different purposes.它们有不同的用途。 There are also a few techniques for passing them around.还有一些技术可以传递它们。

I'll try to be exhaustive.我会尽量详尽无遗。

std::array<int, 7>

this is a buffer of 7 int s.这是一个 7 int的缓冲区。 They are stored within the object itself.它们存储在 object 本身中。 Putting an array somewhere is putting enough storage for exactly 7 int s in that location (plus possible padding for alignment reasons, but that is at the end of the buffer).将一个array放在某处就是在该位置为恰好7 int放置足够的存储空间(加上可能出于 alignment 原因的填充,但那是在缓冲区的末尾)。

You use this when, at compile time, you know exactly how big something is, or need to know.当您在编译时确切知道某物有多大或需要知道时,您可以使用它。

std::vector<int>

this object holds ownership of a buffer of int s.这个 object 拥有一个int缓冲区的所有权。 The memory that holds those int s is dynamically allocated and can change at runtime.保存这些int的 memory 是动态分配的,可以在运行时更改。 The object itself is usually 3 pointers in size. object 本身的大小通常是 3 个指针。 It has some strategies to grow that avoids doing N^2 work when you keep adding 1 element at a time to it.它有一些增长策略,可以避免在您一次向其添加 1 个元素时进行 N^2 工作。

This object can be efficiently moved -- it will steal the buffer if the old object is marked (by std::move or other ways) as being safe to steal state from.这个 object 可以有效地移动——如果旧的 object 被标记(通过std::move或其他方式)可以安全地从中窃取 state,它将窃取缓冲区。

std::span<int>

This represents an externally owned sequence of int s, possibly stored in a std::array or owned by a std::vector , or stored somewhere else.这表示外部拥有的int序列,可能存储在std::array中或由std::vector拥有,或存储在其他地方。 It knows where in memory it starts and when it ends.它知道它在 memory 中的何处开始以及何时结束。

Unlike the two above, it is not a container, but a range or a view of the contents.与上面两个不同的是,它不是容器,而是内容的范围或视图。 So you can't assign spans to each other (the semantics are confusing), and you are responsible to ensure that the source buffer lasts "long enough" that you don't use it after it is gone.所以你不能互相分配跨度(语义混乱),你有责任确保源缓冲区持续“足够长”,以至于你在它消失后不再使用它。

span is often used as a function argument. span通常用作 function 参数。 In your case, it probably solves most of your problem -- it lets you pass arrays of different sizes to a function, and within that function you can read or write the values.在你的情况下,它可能解决了你的大部分问题 - 它允许你将不同大小的 arrays 传递给 function,并且在该 function 中你可以读取或写入值。

span followed pointer semantics. span遵循指针语义。 That means const std::span<int> is like a int*const -- the pointer is const , but the thing pointed to is not!这意味着const std::span<int>就像一个int*const ——指针const ,但指向的东西不是! You are free to modify the elements in const std::span<int> .您可以自由修改const std::span<int>中的元素。 In comparison, std::span<const int> is like a int const* -- the pointer is not const, but the thing pointed to is.相比之下, std::span<const int>就像一个int const* ——指针不是 const,但指向的东西是。 You are free to change what range of elements the span refers to in std::span<const int> , but you aren't allowed to modify the elements themselves.您可以自由更改 span 在std::span<const int>中引用的元素范围,但您不能修改元素本身。

A final technique is auto or templates.最后一种技术是auto或模板。 Here we implement the body of the function in the header (or equivalent) and leave the type unconstrained (or, constrained by concepts).在这里,我们在 header(或等效项)中实现 function 的主体,并使类型不受约束(或受概念约束)。

template<std::size_t N>
int total0( std::array<int, N> const& elems ) {
  int r = 0;
  for (int e:elems) r+=e;
  return r;
}

int total1( std::vector<int> const& elems ) {
  int r = 0;
  for (int e:elems) r+=e;
  return r;
}

int total2( std::span<int const> elems ) {
  int r = 0;
  for (int e:elems) r+=e;
  return r;
}

int total3( auto const& elems ) {
  int r = 0;
  for (int e:elems) r+=e;
  return r;
}

template<class Ints>
int total4( Ints const& elems ) {
  int r = 0;
  for (int e:elems) r+=e;
  return r;
}

notice these all have the same implementation.注意这些都有相同的实现。

total3 and total4 are identical; total3total4相同; you need a more modern compiler to use total3 syntax.你需要一个更现代的编译器来使用total3语法。

total1 and total2 allow you to split the implementation into a cpp file away from the header file. total1total2允许您将实现从 header 文件拆分到一个 cpp 文件中。 Also, code generation isn't done for different arguments.此外,不会为不同的 arguments 生成代码。

total0 , total3 and total4 all result in different code to be generated based on the type of the arguments. This can cause binary bloat issues, especially if the body was more complex than shown, and causes build time problems in larger projects. total0total3total4都会导致根据 arguments 的类型生成不同的代码。这可能会导致二进制膨胀问题,尤其是当主体比显示的更复杂时,并且会导致大型项目中的构建时间问题。

total1 won't work with a std::array directly. total1不能直接与std::array一起使用。 You can do total1({arr.begin(), arr.end()}) which would copy the contents to a dynamic vector before using the code.您可以执行total1({arr.begin(), arr.end()}) ,这会在使用代码之前将内容复制到动态向量中。

Finally, note that span<int> is the closest you get to the C way of arr[], size .最后,请注意span<int>是最接近arr[], size的 C 方式的。 Span is, in essence, a pointer-to-first and length pair, with utility code wrapping it. Span 本质上是指向第一个指针和长度的指针对,实用程序代码将其包装起来。

The main purpose of a C++11 std::array<> is to be a decent replacement for C-style arrays [] , especially when they're declared with new and dismissed with delete[] . C++11 std::array<>的主要目的是成为 C 风格 arrays []的一个不错的替代品,尤其是当它们用new声明并用delete[]消除时。

The main goal here is to get an official, managed object that serves as an array, while maintaining as constant expressions everything that can be.这里的主要目标是获得一个官方的、托管的 object 作为数组,同时将所有可能的内容维护为常量表达式。

Principal issues with regular arrays is that since they're not actually objects, one cannot derivate a class from them (forcing you to implement iterators) and are a pain when you copy classes that uses them as object properties.常规 arrays 的主要问题是,由于它们实际上不是对象,因此无法从它们派生出 class(迫使您实现迭代器),并且在复制将它们用作 object 属性的类时会很痛苦。

Since new , delete and delete[] return pointers, you need each time either to implement a copy constructor that will declare another array them copy its content or maintaining your own dynamic reference counter on it.由于newdeletedelete[]返回指针,您每次都需要实现一个复制构造函数,该构造函数将声明另一个数组并复制其内容,或者在其上维护您自己的动态引用计数器。

From this perpective, std::array<> is a good way to declare purely static arrays that will be managed by the language itself.从这个角度来看, std::array<>是声明纯 static arrays 的好方法,它将由语言本身管理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM