Reputation: 4593
If I want to build a very simple array like:
int myArray[3] = {1, 2, 3};
Should I use std::array
instead?
std::array<int, 3> a = {{1, 2, 3}};
What are the advantages of using std::array
over usual ones? Is it more performant? Just easier to handle for copy/access?
Upvotes: 173
Views: 100004
Reputation: 39385
std::array
solves numerous issues which C-style arrays have. It's not about performance for the most part; it's about correctness and convenience. Here is the list of issues with C-style arrays that std::array
solves.
This one is particularly annoying. Especially for tasks like initializing look-up tables, returning arrays is useful:
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
According to the C declaration syntax, this is how a function get_array
which returns an array of 10 int
s would be declared.
This syntax is very surprising, and one of the reasons why it's very unlikely to ever be standardized in C.
int arr[] = {0, 1, 2, 3};
arr = something_else; // ill-formed
Assigning arrays is fairly common for small arrays, such as pairs or triples.
This feels particularly inconsistent because the syntax =
can be used for initialization.
int data[] = {0, 1};
int copy[] = data;
This is yet another restriction which feels arbitrary. What makes it even worse is that copy-initialization is valid for arrays in principle, just not using another array instead of a braced-init-list.
==
is a common mistakeint arr[] = {0, 1, 2, 3};
arr == arr; // true, but doesn't compare contents, but is pointer comparison
This is a common mistake, which is why array comparison was deprecated in C++23 and is likely going to be removed in C++26.
See also Why does == equality comparison between arrays not work?
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 "
The list goes on and on. The fact that arrays decay to pointers often leads to counter-intuitive behavior. Most of this behavior is not yet deprecated.
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
Without compiler warnings (-Wvla
for GCC/clang), we can inadvertently create a VLA.
This makes code non-portable because not every compiler has support for VLAs.
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
}
Parameters of array type in function parameters are adjusted to pointer types.
This means that sizeof(arr)
doesn't work properly, and the provided size [4]
is actually meaningless.
This is very surprising, and using sizeof
in conjunction with pointers when arrays were expected is one of the most common beginner mistakes in 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
This issue is a consequence of the type adjustment to pointers in the prior point. There is no array type in the parameter from which N
could be deduced. A workaround is necessary, but this workaround is not pretty.
Due to all the aforementioned irregularities, arrays require special cases in generic code. To name some examples:
std::swap
must have an overload for arrays because arrays are immovablestd::size
, std::begin
, std::empty
, etc. are necessary that have a special case for arraysAny developer who writes a library which contains range.begin()
instead of std::begin(range)
has to fear their code breaking if a user uses C-style arrays.
Of course, using std::array
doesn't solve the underlying issue, but it means that you never suffer from a library developer having made the false assumption that .begin()
is always valid.
Furthermore, these countless special cases still don't cover everything. You can use std::swap
to do an element-wise swap of two arrays, but you cannot use std::exchange
to exchange arrays element-wise (because arrays cannot be returned from functions).
It is easy to inadvertently write code which has performance penalties caused by aliasing. Consider the following functions which are meant to serialize a 32-bit unsigned integer array into a byte array with little endian byte order.
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]);
}
}
See live example at Compiler Explorer
Intuitively, it would seem like both code samples are doing pretty much the same thing. However, the code generation for the second sample is much worse. There is absolutely no auto-vectorization; it's an extremely naive loop.
std::array
can often indicate to the compiler that no aliasing takes place, or if there is overlap between memory regions, it cannot be partial. This is a boost to optimizations.
std::array
solves countless problems with C-style arrays.
For the most part, this isn't about performance, although std::array
may be better in specific cases.
It's about C-style arrays having confusing syntax, common pitfalls, arbitrary restrictions, and other issues.
Upvotes: 19
Reputation: 19
While std::array
has some advantages, as outlined in the other helpful answers, some have stated things like, "… there is no performance difference than a raw array."
This simply is not true and is misleading to anyone working in true embedded development (time critical, bare metal).
Quick and dirty test using an STM32G0B1 running at 50MHz (CPU): Writing to a std::array
takes approximately 5 µS longer than to a raw C style array. To me, this is a significant performance difference that cannot be ignored.
Upvotes: 1
Reputation:
You will get the same perfomance results using std::array
and c array
If you run these code:
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;
You will get these results:
std::array<QPair<int, int> 5670 milliseconds
QPair<int, int> took 5638 milliseconds
Mike Seymour is right, if you can use std::array
you should use it.
Upvotes: 2
Reputation: 56547
A std::array
is a very thin wrapper around a C-style array, basically defined as
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
};
It is an aggregate, and it allows you to use it almost like a fundamental type (i.e. you can pass-by-value, assign etc, whereas a standard C array cannot be assigned or copied directly to another array). You should take a look at some standard implementation (jump to definition from your favourite IDE or directly open <array>
), it is a piece of the C++ standard library that is quite easy to read and understand.
Upvotes: 67
Reputation: 421
Is it more performant ?
It should be exactly the same. By definition, it's a simple aggregate containing an array as its only member.
The situation seems to be more complicated, as std::array
does not always produce identical assembly code compared to C-array depending on the specific platform.
I tested this specific situation on 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 and Clang produce identical assembly code for both the C-array version and the std::array
version.
MSVC and ICPC, however, produce different assembly code for each array version. (I tested ICPC19 with -Ofast
and -Os
; MSVC -Ox
and -Os
)
I have no idea, why this is the case (I would indeed expect exactly identical behavior of std::array and c-array). Maybe there are different optimization strategies employed.
As a little extra: There seems to be a bug in ICPC with
#pragma simd
for vectorization when using the c-array in some situations
(the c-array code produces a wrong output; the std::array
version works fine).
Unfortunately, I do not have a minimal working example for that yet, since I discovered that problem while optimizing a quite complicated piece of code.
I will file a bug-report to intel when I am sure that I did not just misunderstood something about C-array/std::array
and #pragma simd
.
Upvotes: 38
Reputation: 9770
std::array
has value semantics while raw arrays do not. This means you can copy std::array
and treat it like a primitive value. You can receive them by value or reference as function arguments and you can return them by value.
If you never copy a std::array
, then there is no performance difference than a raw array. If you do need to make copies then std::array
will do the right thing and should still give equal performance.
Upvotes: 13
Reputation: 254411
What are the advantages of using
std::array
over usual ones?
It has friendly value semantics, so that it can be passed to or returned from functions by value. Its interface makes it more convenient to find the size, and use with STL-style iterator-based algorithms.
Is it more performant ?
It should be exactly the same. By definition, it's a simple aggregate containing an array as its only member.
Just easier to handle for copy/access ?
Yes.
Upvotes: 185
Reputation: 50043
std::array
is designed as zero-overhead wrapper for C arrays that gives it the "normal" value like semantics of the other C++ containers.
You should not notice any difference in runtime performance while you still get to enjoy the extra features.
Using std::array
instead of int[]
style arrays is a good idea if you have C++11 or boost at hand.
Upvotes: 39