Suppose you want to have a static array of pre-defined values/objects (const or non-const) associated with a class. Possible options are to use std:vector
, std::array
or C-style array (ie. [])
, or . For example,
In .hpp:
class MyClass {
public:
static const std::vector<MyClass> vec_pre; // No efficient way to construct with initializer list, since it always uses Copy Contructor, even when using std::move
static const std::array<MyClass, 2> arr_pre; // Have to specify size which is inconvenient
static const MyClass carr_pre[]; // Not compatible with C++11 for-range since size is undefined
};
In .cpp
const std::vector<MyClass> MyClass::vec_pre = { std::move(MyClass{1,2,3}), std::move(MyClass{4,5,6}) }; // NOTE: This still uses copy constructor
const std::array<MyClass, 2> MyClass::arr_pre= { MyClass{1,2,3}, MyClass{4,5,6} };
const ZwSColour ZwSColour::carr_pre[] = { MyClass{1,2,3}, MyClass{1,2,3} }
When writing this intially, I chose the std::vector
since I don't have to specify the size, I get all the goodness of the vector class, and it seems like the modern C++ way to do it. PROBLEM: while testing, I noticed that it would call the Move contructor, but then still call the Copy constructor for each element. The reason for this is the std::initializer_list
only allows const access to its members, and so the vector has to copy them from the initializer_list to its own storage. Even though it's only done once at startup, this is inefficient and there doesn't appear to be a way around it, so I looked at other options (std::array
and C-array[]
).
Second choice was to use std::array
, which is also a modern C++ way, and it doesn't suffer from the problem of calling the Copy Constructor for each value since it doesn't need to create copies (Not sure why though exactly ?). std::array
also has the benefit that you don't need to wrap each value in std::move()
. However, it has the annoyance that you have to specify the size first, so every time you add/remove elements, you have to change the size as well. There are ways around this but none of them are ideal. As @Ricky65 states, you should just be able to do
std::array <int> arr = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 }; //automatically deduces its size from the initializer list :)
This leaves me with the last option - the good old C-style array [] - which has the benefits that I don't have to specify the size, and is efficient in that it doesn't call the Copy constructor for each object. Downsides are that it's not really modern C++, and the biggest downside is that if you don't specify the size of the array in the .hpp header, then the C++11 for-range doesn't work as the compiler complains
Cannot use incomplete type 'const MyClass []' as a range
You can overcome this error by either specifying the size of the array in the header (but this is inconvenient and produces hard-to-maintain code, as you need to adjust the size every time you add/remove items from the initializer list), or alternatively use constexpr
and completely declare the array and values in the .hpp header
constexpr static MyArray my_array[] = { MyClass{1,2,3}, MyClass{4,5,6} };
NOTE: The constexpr "work-around" will only work for POD's and so cannot be used in this case for Class objects. The above example will result in a compile-time error Invalid use of incomplete type 'MyClass'
I'm trying to write best-practice modern C++ where possible (eg. using copy-and-swap idiom), and so wonder what is the best way to define static arrays for a class...
- without having to specify the size
- which don't need to be Copy constructed (or Move constructed either, if possible)
- which can be used with C++ for-range
- which don't need to be specified in the header file
- Should compile/work for Clang/LLVM 3.5, Visual Studio 2013 Update 4 RC, and GCC 4.8.1.
EDIT1: Another post about vector problem of not being able to move values from initializer list
EDIT2: More info on using std::array without the need to specify size, which also creates/uses make_array(), and mentions that there is a proposal for make_array() to become a a standard. Original SO link courtesy of comment by @Neil Kirk.
EDIT3: Another problem with the vector
method (at least in this case) is that you cannot iterate over the items using a const T
or T
. It only allows iteration using const T&
(when it's static const
) and const T&
/T&
(when it's static
). What's the reason for this limitation ?
Descriptive Answer to solutions
@Yakk's solution appears to be the only solution, and also works on Visual C++ 2013 Update 4 RC.
I find it staggering that such a trivial issue is so difficult to implement using the latest C++11/14 standard.
std::move(MyClass{1,2,3})
is superfluous becauseMyClass{1,2,3}
is already an rvalue. – Exultstd::array
doesn't need to create copies (Not sure why though exactly?) Probably the implementation can just take ownership of the internal array of theinitializer_list
as an optimization. – Exultstd::move()
being superfluous - when it's used, the move constructor does get called, followed by the copy constructor. Why is it the move constructor even gets called if it's superfluous ? Shouldn't this be optimized away ? – Bazarstd::vector
must use a copy constructor because it does not take ownership of the items in theinitializer_list
, where asstd::array
does take ownership of those items. DarkMatter's link explains this. – Sisselstd::array
. – Exultstd::vector<MyClass>{MyClass{1,2,3}}
first movesMyClass{1,2,3}
into aninitializer_list
, then copies theinitializer_list
into thevector
. Usingstd::move
won't change that. – Exultstd::move
in this case means that the conditions are not met which would otherwise allow the compiler to elide a move constructor with side-effects (§12.8/31). – Exult