The use of C-style arrays is considered technical debt in modern C++. C arrays are inconvenient to use and are sources of bugs but even today I continue to see a lot of new code that uses them. By following these tips you will be able to significantly improve the quality of your code in just a few simple steps.

Array problems

Array problems can be divided into two broad categories:

1) access beyond the limits;
2) oversizing.

The first is when the programmer does not correctly calculate the size of the array, the second is when the programmer does not know in advance the number of elements to be managed and therefore uses a “fairly” large number.

Let’s look at a few examples:

    #define NUMPAD_DIGITS 9
void create_widgets() {
   Widget *widgets[NUMPAD_DIGITS + 1];
   for (int i = 0; i < NUMPAD_DIGITS; i++)
       widgets[i] = new Widget();
}
void foo_no_size(const char *arr) {
   int a[sizeof(arr)]; // error: should be strlen(arr) + 1
}
 
void foo_size(const char *arr, int count) {
   int a[sizeof(arr)]; // error: should be count
}
void read_data() {
  char buffer[256];
  // read from socket
}
void main() {
   int arr[2] {1,2};
   int arr2[2] {1,2};
   if (arr == arr2) { // Error: never true
       cout << "Arrays are equal\n";
   }
}

These problems derive from automatic behaviour of the arrays called “decay” (pointer deterioration): any array can be assigned to a pointer of the same type, thus losing the size of the array, without this being a compilation error.

Another problem resulting from pointer “decay” is the comparison between arrays. Typically, when comparing two arrays, the aim is to compare the value of the elements contained in the array, but the default behaviour is the comparison between pointers.

Finally, I can’t use assignment to make a member-to-member copy between arrays.

All the alternatives presented in this article do not automatically “decay” the pointer, thus keeping information on the size, and offer operators for comparison and assignment.

Possible solutions

Let’s look at how it is possible to overcome these problems both in STL and in in Qt.

Use std::vector

Paraphrasing a well-known saying, no-one has ever been fired for using a vector. Its features make it the right data structure for 90% of use cases and this should be your default choice (as suggested by Herb Sutter in “C++ Coding Standards: 101 Rules, Guidelines, And Best Practices”). Let’s look at some of them together:

Let’s look at how the examples presented in the previous paragraph can be rewritten.

#define NUMPAD_DIGITS 10
void create_widgets() {
  vector<Widget*> widgets;
  for (int i = 0; i < NUMPAD_DIGITS; i++)
      widgets.push_back(new Widget);
}
 
void foo_no_size(vector<char> v) {
   vector<int> a(v.size());
}

Use std::array

If you can’t use dynamic allocation, starting with C++ 11 the standard library provides a fixed-size container that knows its size: std::array.

The advantages of std::array over a C array are:

This container is very easy to use, let’s look at an example:

int main()
{
   std::array<int, 3> a2 = {1, 2, 3};
   std::array<std::string, 2> a3 = { std::string("a"), "b" };
   // container operations are supported
   std::sort(a1.begin(), a1.end());
 
   // ranged for loop is supported
   for(const auto& s: a3)
       std::cout << s << ' ';
   std::cout << '\n';
   std::array<int, 3> a4 = a1; // Ok
   //std::array<int, 4> a5 = a1; // Error, wrong number of elements
}

We can rewrite the examples presented in the introduction in a more idiomatic way as follows:

#define NUMPAD_DIGITS 10
void create_widgets() {
   array<Widget*, NUMPAD_DIGITS> widgets;
   for (auto &w : widgets)
       w = new Widget;
}

Use QVector

If you are writing a Qt program, the default data structure can be either std::vector or QVector. QVector provides all the guarantees of std::vector and in addition guarantees API-level compatibility with the rest of the Qt program.

If you are a long-time Qt user, perhaps your default choice falls on QList. However, this choice must be reconsidered because there are limitations that impact on performance, both in terms of speed and memory occupied. In fact, to date QList should only be used to interact with the Qt APIs that require it.
Personally, I prefer to use QVector than std::vector because I find the API more comfortable to use.

Compatibility with C code

At this point I hope I have convinced you that there is no reason to use C arrays in C++. In some cases, however, you should be able to interact with C APIs that read or write on C arrays, for example strncpy(char *dest, const char *src, size_t n).

All the containers I have described have compatibility methods with C arrays precisely to handle cases like this.

Let’s look at some examples:

int main()
{
   constexpr size_t len = 14;
   const char s[len] = "Hello world!\n";
   array<char, len> a;
   strncpy(a.data(), s, a.size());
  
   vector<char> v(len); // allocate space for `len` elements
   strncpy(v.data(), s, v.size());
 
   QVector<char> v(len); // allocate space for `len` elements
   strncpy(v.data(), s, v.size());
}

The data() method returns a pointer to the first element of memory and the size() method returns the number of available elements.

Note that I used a vector constructor which creates a number of elements before calling the function, otherwise size() is 0.

Conclusions

In C++ there are no plausible reasons to use C-style arrays. The solutions we have looked at in this article are all type and size safe, have additional features (such as automatic resize management or comparison operators) and are compatible with C arrays in cases where it is necessary to interact with C libraries.

std::vector is the correct replacement for most cases, QVector is preferable for code that uses Qt, while std::array can be used in those cases where granular memory control is critical.