C++ std::vector vs raw array for short arrays

Started by
6 comments, last by SeanMiddleditch 9 years, 8 months ago

Hello everyone,

I need an array of 2 booleans inside class.


bool array[2];

I don't have C++11 and I cannot use initializer lists so I have to initalize array like this:


Class::Class()
{
  array[0]=array[1]=false;
}

If array was std::vector<bool>,I could do:


Class::Class() :
  array(2,false)
{}

But it's length is just 2,and I don't need any of the std::vector stuff (such as iterators ... )

Which one is a better approach?

Thanks,

MatejaS

Advertisement
A std::vector is a kind of dynamic array. One of the key aspects is that the size can be changed.

Even with a standard array you have iterators. The other features you mentioned are mostly just syntactic candy. If you need to initialize each one then you do it. Passing it to the constructor of a vector just means that the constructor is running that loop instead of you.

If you have no need to resize the container then a simple fixed array works just fine.

If you do need to resize the container use the vector class or a container that works better for your usage patterns. The vector class will do all the work necessary to resize it, including the process of allocating new memory, moving all the objects over to the new block of memory, and releasing the old block of memory.

If the size of the array will not be changed at runtime and you don't need any of the other functionality that comes along with a vector then there is no good reason to use one. If at some later time you wish to change your array to a vector you should be able to do so with minimal impact to your code, as it overloads the subscript operator.

std::fill() would be a standard and clear way to specify intent when initializing an array:
Class::Class()
{
  std::fill(array, array + 2, false);
}

At the same time, I wonder if a two-element array is actually appropriate, rather than two separately named bools. If you have multiple two-element arrays, it might be cleaner to wrap one element of each into a struct, and then just have a single two-element array of the struct. Each instance of the struct could then have its own default constructor that would initialize the bool to false, and any other variables to their proper defaults also.
"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas only, and not for things themselves." - John Locke
Not sure if multithreading is an issue here, but be aware that for:
bool array[2];
the assignment
 array[0] = true;
is atomic.

std::vector<> on the other hand has a template specialization for bools, which merges them into integer values to safe space. Using std::vector<bool>
std::vector<bool> vecArray;
//...
vecArray[0] = true;
becomes:
unsigned char tmp = vecArray[0];
tmp = tmp | (1 << 0);
vecArray[0] = tmp;
which is not atomic.

Edit: On the notion of merging the bools into integer types, have you considered C-style flags or, for a more array like experience, std::bitset?

Why exactly can't you brace-initialize this array? That isn't a C++11 only thing.

C++03 8.5.1/1 [dcl.init.aggr] states:

An aggregate is an array or a class (clause 9) with no user-declared constructors (12.1), no private or protected
non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).

All of that is certainly true for an array of bool with size 2.

Therefore, 8.5.1/2, which states that aggregates can be brace-initialized is applicable.

bool array[2] = {}; should compile just fine (and it does indeed compile fine using GCC with either --std=c++03 or --std=c++98).

Not exactly relevant, but also note that array[0]=array[1]=false; most probably doesn't initialize the array in the order you expect.

Why exactly can't you brace-initialize this array? That isn't a C++11 only thing.

It is when your array is a member variable and you're trying to initialize it in a constructor.

Why exactly can't you brace-initialize this array? That isn't a C++11 only thing.

...

Therefore, 8.5.1/2, which states that aggregates can be brace-initialized is applicable.

bool array[2] = {}; should compile just fine (and it does indeed compile fine using GCC with either --std=c++03 or --std=c++98).

Not exactly relevant, but also note that array[0]=array[1]=false; most probably doesn't initialize the array in the order you expect.

Were you declaring that as a member variable, or just a local variable? From what I understand, non-static inline member initialization is also a C++11 thing. Without that feature, the member variable needs to either be initialized in the constructor's initializer list, or within the constructor body, neither of which would support the syntax provided in your post.

Edit: Ninja-ed by SiCrane!

"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas only, and not for things themselves." - John Locke
Don't forget std::array, which can be easily emulated if your compiler/stdlib doesn't ship with it.

It gives you all the advantages of std::vector (consistent C++ interface) and the advantages of a C array (fixed size, no dynamic free store allocation, no extraneous size/capacity/pointer space taken up). You can use std::array::fill, e.g.:

class foo {
std::array<int, 2> _stuff;

public:
foo() {
  _stuff.fill(1);
};
There is no constructor to pre-initialize the values other than via initializer lists.

Note that you can also easily fill in a C array with a single value in C++ in a very similar way, too:

int array[4];

// fill all four elements with 1
std::fill(std::begin(array), std::end(array), 1);
Remember that std::begin and std::end are free functions that return a begin and end iterator (respectively) for their argument and should be used from now on in place of the member function variants. Normally you should not explicitly use their namespace so that they work with ADL, but primitive types (like C arrays) don't participate in ADL so you need them here (or you need "using std::begin"/"using std::end" in scope).

And again, if your compiler lacks these, you can write them easily (and they're worth having) for C arrays, along with a handy size function (and you can support general containers with a little overloading and SFINAE work, which I leave as an exercise for the reader):

template <typename T, size_t N>
T* begin(T(&v)[N]) { return v; } 

template <typename T, size_t N>
T* end(T(&v)[N]) { return v + N; } 

template <typename T, size_t N>
N size(T(&)[N]) { return N; }

Sean Middleditch – Game Systems Engineer – Join my team!

This topic is closed to new replies.

Advertisement