如果我想要创建一个非常简单的数组,例如:
int myArray[3] = {1,2,3};
我应该使用 std::array
吗?
std::array<int, 3> a = {{1, 2, 3}};
使用std::array
相对于普通数组而言有什么优势?它在性能上更高吗?只是更容易进行复制/访问处理吗?
使用
std::array
相比普通数组有哪些优势?
它具有友好的值语义,因此可以通过值传递到函数中或从函数中返回。其接口使得查找大小更加方便,并且可以与STL风格的基于迭代器的算法一起使用。
它的性能更高吗?
它应该完全相同。按定义,它是包含数组作为唯一成员的简单聚合。
只是更容易进行复制/访问吗?
是的。
std::array
是一个非常薄的包装器,用于C风格数组,基本上定义为
template<typename T, size_t N>
struct array
{
T _data[N];
T& operator[](size_t);
const T& operator[](size_t) const;
// other member functions and typedefs
};
它是一个聚合类型,几乎可以像基本类型一样使用(即可以按值传递、赋值等,而标准 C 数组不能直接复制或分配到另一个数组)。您应该查看一些标准实现(从您喜欢的 IDE 跳转到定义,或直接打开<array>
),这是 C++ 标准库中相当容易阅读和理解的一部分。
class...{public:...}
与结构体具有相同的访问规则;) - vsoftcostd::array
用作C数组的零开销包装器,为其提供了其他C++容器的“正常”值语义。
在您仍然可以享受额外功能的同时,不应该注意到任何运行时性能差异。
如果您有C++11或boost,则使用std::array
而不是int []
样式数组是一个好主意。
它的性能更好吗?
它应该完全相同。根据定义,它是一个简单的聚合体,只包含一个数组成员。
情况似乎更加复杂,因为 std::array
在特定平台上与 C 数组相比并不总是生成相同的汇编代码。
我在 godbolt 上测试了这种情况:
#include <array>
void test(double* const C, const double* const A,
const double* const B, const size_t size) {
for (size_t i = 0; i < size; i++) {
//double arr[2] = {0.e0};//
std::array<double, 2> arr = {0.e0};//different to double arr[2] for some compiler
for (size_t j = 0; j < size; j++) {
arr[0] += A[i] * B[j];
arr[1] += A[j] * B[i];
}
C[i] += arr[0];
C[i] += arr[1];
}
}
GCC和Clang都为C数组版本和std::array
版本生成相同的汇编代码。
然而,MSVC和ICPC为每个数组版本生成不同的汇编代码。(我测试了带有-Ofast
和-Os
选项的ICPC19;MSVC带有-Ox
和-Os
选项)
我不知道为什么会出现这种情况(我确实期望std::array和c-array完全一致)。也许采用了不同的优化策略。
额外说明:
ICPC似乎存在一个错误
#pragma simd
在某些情况下使用C数组进行向量化时需要注意,因为C数组的代码会产生错误的输出;而std::array
版本可以正常工作。
不幸的是,由于我是在优化一段相当复杂的代码时才发现这个问题的,所以目前我还没有最小可行示例。
等我确定我没有对C数组/std::array
和#pragma simd
有误解后,我会向英特尔报告此问题。
std::array
具有值语义,而原始数组则没有。这意味着您可以复制 std::array
并将其视为基本值。您可以通过值或引用作为函数参数接收它们,并且可以通过值返回它们。
如果您从未复制过 std::array
,那么与原始数组相比,它们的性能没有差异。如果您需要进行复制,则 std::array
将执行正确操作,并应仍然提供相同的性能。
std::array
和c数组
获得相同的性能结果。
如果运行以下代码:std::array<QPair<int, int>, 9> *m_array=new std::array<QPair<int, int>, 9>();
QPair<int, int> *carr=new QPair<int, int>[10];
QElapsedTimer timer;
timer.start();
for (int j=0; j<1000000000; j++)
{
for (int i=0; i<9; i++)
{
m_array->operator[](i).first=i+j;
m_array->operator[](i).second=j-i;
}
}
qDebug() << "std::array<QPair<int, int>" << timer.elapsed() << "milliseconds";
timer.start();
for (int j=0; j<1000000000; j++)
{
for (int i=0; i<9; i++)
{
carr[i].first=i+j;
carr[i].second=j-i;
}
}
qDebug() << "QPair<int, int> took" << timer.elapsed() << "milliseconds";
return 0;
您将得到以下结果:
std::array<QPair<int, int> 5670 milliseconds
QPair<int, int> took 5638 milliseconds
迈克·西莫(Mike Seymour)是正确的,如果您可以使用std::array
,您应该使用它。
constexpr auto lookup = [] {
std::array<int, 128> result;
// compute the contents
return result;
};
int get_array()[10];
auto get_array() -> int[10]; // workaround: trailing return types
get_array
的方式。
这种语法非常令人惊讶,也是为什么它很不可能在C语言中被标准化的原因之一。
int arr[] = {0, 1, 2, 3};
arr = something_else; // ill-formed
=
可以用于初始化。int data[] = {0, 1};
int copy[] = data;
int arr[] = {0, 1, 2, 3};
arr == arr; // true, but doesn't compare contents, but is pointer comparison
char arr[] = "hello ";
if (arr); // always true, even if array contains an empty string
+arr; // OK, but what does it mean to apply unary plus to an array?!
arr + 1; // OK, but result is not "hello 1", it is "ello "
int size = rand();
int vla[size]; // could be OK if the compiler supports VLAs as an extension
std::array<int, size> arr; // error, as expected
-Wvla
),我们可能会无意中创建一个VLA。这使得代码不可移植,因为并非每个编译器都支持VLA。
void foo(int arr[4]) { // equivalent to accepting a parameter of type int*
sizeof(arr) / sizeof(arr[0]); // = sizeof(void*) / sizeof(int), most likely 2
}
void foo(std::array<int, 4> arr) {
arr.size(); // 4, correct
}
sizeof(arr)
无法正常工作,并且提供的大小[4]
实际上是没有意义的。sizeof
结合使用是C语言中最初级的错误之一。
template <std::size_t N>
void foo(int arr[N]); // N can't be deduced from the array parameter
template <std::size_t N>
void foo(int (&arr)[N]); // workaround with complicated syntax
std::swap
必须对数组进行重载,因为数组是不可移动的
- 数组不是类,所以需要很多自由函数,如std::size
、std::begin
、std::empty
等,它们对数组有特殊处理的情况range.begin()
而不是std::begin(range)
,就必须担心他们的代码会在用户使用C风格数组时出错。
当然,使用std::array
并不能解决根本问题,但它意味着你永远不会遭受库开发者错误地假设.begin()
总是有效的问题。std::swap
对两个数组进行逐元素交换,但你不能使用std::exchange
对数组进行逐元素交换(因为数组不能从函数中返回)。std::array<std::byte, 4> serialize_le(unsigned x) {
return {
std::byte(x >> 0),
std::byte(x >> 8),
std::byte(x >> 16),
std::byte(x >> 24)
};
}
void write_le_n(std::byte* mem, std::array<unsigned, 1024>& numbers) {
for (unsigned n : numbers) {
auto bytes = serialize_le(n);
std::memcpy(mem, &bytes[0], sizeof(bytes));
mem += 4;
}
}
void write_le(std::byte mem[], unsigned x) {
mem[0] = std::byte(x >> 0);
mem[1] = std::byte(x >> 8);
mem[2] = std::byte(x >> 16);
mem[3] = std::byte(x >> 24);
}
void write_le_n(std::byte* mem, unsigned x[1024]) {
for (unsigned i = 0; i < 1024; ++i) {
write_le(mem + i * 4, x[i]);
}
}
直观地看,这两个代码示例似乎做的事情基本相同。然而,第二个示例的代码生成要糟糕得多。 完全没有自动向量化;这是一个极其简单的循环。
std::array
通常可以告诉编译器没有别名发生,或者如果内存区域之间存在重叠,它不能是部分的。这对优化是一种提升。
std::array
解决了使用C风格数组时的无数问题。
在大多数情况下,这与性能无关,尽管在特定情况下std::array
可能更好。
这是因为C风格数组具有令人困惑的语法、常见陷阱、任意限制和其他问题。
虽然std::array
有一些优点,如其他有用的答案所述,但有些人声称,“……与原始数组相比没有性能差异。”
这简直是不真实的,对于任何从事真正嵌入式开发(时间关键、裸机)的人来说都是误导性的。
在一个运行在50MHz(CPU)的STM32G0B1上进行快速而简单的测试:向std::array
写入大约需要比向原始C样式数组多5微秒。对我来说,这是一个显著的性能差异,不能被忽视。
for (auto i = ++std::begin(myArray); . . .
可能甚至无法编译(至少在clang 6下似乎基本类型的临时变量不可变)。 - Patrick Frombergstruct Direction { int32_t dw; int32_t dh; };
和static const Direction DIRECTIONS[DIRECTIONS_COUNT] { { -1, 1}, {0,1}, {1,1} , { 1, 0 }, {1,-1}, {0,-1} , {-1,-1}, {-1,0} };
可以编译通过。但是如果你改成一个带有相同初始化列表的std::array<Direction,DIRECTIONS_COUNT>
,突然间你会得到“太多的初始化器”错误。(VS 2019 Community with language = C++17) - BitTicklerstd::array
只是C风格数组的包装器。如果我没记错,在一些早期版本的C++中,你不能用单括号初始化它。然而,不要完全相信我的话,因为我没有经常使用它们。 - H-005