A Glimpse into C++14: Combine Flexibility and Performance with Dynamic Arrays and Runtime-Sized Arrays

C99 introduced the notion of variable length arrays: stack allocated built-in arrays whose size is determined at runtime. C++ lacks a similar feature, to the discontent of many a programmer. However, two recent proposals for adding dynamic arrays and runtime-sized arrays to C++14 are closing the gap at last. Learn how to use these new features to imitate C99’s variable length arrays in C++.

Just as you’ve grown used to C++11, a new standard dubbed C+14 is seen on the horizon. Fret not, though, for the new standard doesn’t bring any revolutionary features such as rvalue references or multithreading. C++14 mainly consists of technical fixes and tweaks to the current C++11 standard.

However, C++14 also includes some new features, including a class template that simulates dynamic arrays and a core language feature known as runtime-sized arrays with automatic storage duration. Both of these provide mechanisms for creating array-like objects and arrays whose sizes are determined at runtime, very much like C99’s variable length arrays (VLAs). In this article I explain these new C++14 features and how they differ from traditional Standard Library containers and built-in static arrays, respectively.

Before looking at these exciting new features let’s see why traditional C++ containers don’t fit the bill.

In Quest of Dynamic Arrays

Seemingly, you can stick to std::vector as an alternative to VLAs. In fact, at one time the C++ standards committee considered replacing built-in arrays with vectors all across the board!

Then again, vectors are not arrays. They allocate their objects on the free-store exclusively (whereas arrays’ storage is not restricted to a specific storage class). Second, std::vector defines the member function resize() that can change a vector’s size. resize() violates a crucial array invariant, namely its immutable size. For these reasons, you can’t use std::vector as a VLA substitute.

At this stage, you might consider using std::array. std::array satisfies all of the requirements of a container. As with built-in arrays, it may store its data on the stack or on the free-store. Let’s look at an example of std::array usage:

#include <array>

#include <iostream>

int main()

{

 //allocate on the stack

std::array<int, 10> vals{}; 

for (int count=0; count<vals.size(); ++count)

  vals[count]=count+1;

std::cout<<"the first value is: "    

  <<vals.front()<<std::endl;

std::cout<<"the last value is: "<<vals[(vals.size()-1)]  

  <<std::endl;

std::cout<<"the total number of elements is: " <<vals.size()<<std::endl;

int* alias=vals.data(); 

std::cout<<"the third element is: "<<alias[2]<<std::endl;

}

There is, however, one problem here. std::array supports only static initialization. You can’t specify its size at runtime:

void func(int sz)

{

 std::array <int,sz> arr;//sz isn’t a constant expression

}

std::array may be a reasonable substitute for built-in arrays whose size is known at compile-time. However, if a dynamic array is what you’re after, you need to look elsewhere.

The standard std::valarray container won’t do either. It supports dynamic initialization. However, it also defines resize().

Let’s face it: The C++11 Standard Library doesn’t have a proper substitute for C99’s VLAs. This is why committee members came up with two new proposals. The first proposal introduces a new container class called std::dynarray. The second introduces runtime-sized arrays with automatic storage duration. Both proposals were approved in April 2013 at the Bristol meeting. Let’s look at std::dynarray first.

To Have and to Hold

A container-based approach offers two advantages:

  • Seamless interfacing with standard iterators and algorithms
  • Code safety, particularly with respect to range-checking and copying

The newly-added std::dynarray container provides the familiar interface of a Standard Library container, albeit with two restrictions:

  • Once the container is initialized, its size cannot change throughout the container’s lifetime.
  • The container object shall not be restricted to the free-store. It may as well reside on the stack – at the discretion of the implementation.

The std::dynarray constructor takes a parameter indicating the number of elements in the container. There is no default constructor since there is no accepted default size for a dynarray object. (Note that you cannot instantiate an empty dynarray and populate it later. However, zero-sized dynarray objects are permitted.)

Additionally, std::dynarray supports copy construction. This implies that elements of a dynarray object must also be copy-constructible. The proposal doesn’t mention move semantics, which is no surprise. Moving from a std::dynarray might violate its size invariant.

std::dynarray provides random access iterators and reverse iterators. However, it doesn’t support construction from an initializer_list nor does it provide a constructor from first and last forward iterators. While the last two restrictions might seem limiting, the rationale behind them is as follows: initializer_list necessarily has a static number of elements. In such cases, a built-in array or a std::array are probably more appropriate design choices anyway. With respect to construction from first and last iterators, the technical consideration is that std::distance(first, last) is a constant time operation only for random access iterators. Users can calculate this value manually and pass the result to the std::array constructor.

The proposal introduces a new standard header file called <dynarray> that defines the class template std::dynarray. An instance of dynarray<T> stores elements of type contiguously. Thus, if d is a dynarray<T> then it obeys the identity &a[n] == &a[0] + n for all 0 <= n < N. Note that there is no mechanism for detecting whether a std::dynarray object is allocated on the free-store or on the stack.

Putting std::dynarray to Work

The following function creates a loop that traverses a std::dynarray object and prints its elements. Next, the function copy-constructs a new std::dynarray, sorts it, accumulates its elements’ values and finally, accesses the std::dynarray elements using data(), at() and the [] operator:

#include <dynarray> //C++14

#include <iostream>

#include <algorithm>

void show(const std::dynarray <int>& vals )

{

  std::dynarray<int>::const_iterator cit=vals.begin();

  for (; cit != vals.end(); cit++)

    std::cout << " " << *cit;

  std::cout << std::endl;

    //runtime initialization of a new dynarray

  std::dynarray <int> target (vals);

    //algorithms, iterators, data(), & rev iterators

  std::sort(target.begin(), target.end());

  for (int n=0, *p=target.data(); n<target.size(); n++ )

    std::cout << " " << p[n]<<std::endl;

  int res=

     std::accumulate(target.rbegin(),target.rend(),0);

  for (int j=0; j<target.size(); j++) {

    //subscript access and at()

   target.at(j)=j*2;

   std::cout<<" "<<target[j] <<std::endl;

  }

  std::cout<<"total: "<<res<<std::endl;

}

Notice how similar the interfaces of std::dynarray and std::vector are – so much so that you may replace every occurrence of std::dynarray in the listing above with std::vector without breaking the code or changing its semantics. The only crucial difference between std::dynarray and std::vector in this context is that once you instantiate a std::dynarray object, you cannot change its size.

Runtime-Sized Arrays

Runtime-sized arrays offer the same syntax and performance of C99’s VLAs. The std::dynarray facility, on the other hand, offers the robustness of a first class Standard Library container. Additional differences between these two features include:

  • Runtime-sized arrays do not allow zero-sized arrays; std::dynarray does.
  • One can use initializer lists with runtime-sized arrays. These aren’t currently supported with std::dynarray.
  • There are subtle differences with respect to the exceptions that each feature might throw. I won’t get into the details here but remember always to check the exception specification of each core feature and library class you use.

The following code uses a runtime-sized array. Any C++14 compliant compiler should accept it:

//C++14 only

#include <algorithm>

void f(std::size_t n)

{

  int arr[n]; //runtime-sized array

  for (std::size_t i=0; i< n; ++i)

    arr[i] = i*2;

  std::sort(arr, arr+n);

  for (std::size_t i=0; i< n; ++i)

    std::cout<<" "<<arr[i]<<std::endl;

}

Bear in mind that runtime-sized arrays aren’t precisely the same as C99’s VLAs. The C++14 feature is more restrained, which is just as well. Specifically, the following properties are excluded:

  • Runtime-sized multidimensional arrays
  • Modifications to the function declarator syntax
  • sizeof(a) being a runtime-evaluated expression returning the size of a
  • typedef int a[n]; evaluating n and passing it through the typedef

Examples:

void f(std::size_t n)

{

  int a[n]; //C++14

  unsigned int x=sizeof(a);             //ill-formed

  const std::type_info& ti = typeid(a); //ill-formed

  typedef int t[n];                     //ill-formed

}

In Conclusion

Runtime-sized arrays with automatic storage are to std::dynarray as ordinary, statically-sized arrays are to std::array. If you want a low-level, efficient core language arrays whose size is determined at runtime, use them. If however you need a full-blown container that simulates runtime-sized arrays, use std::dynarray instead.

With respect to compiler support of runtime-sized arrays, gcc, Intel C++, and Clang already implement this feature. At present I am not aware of any implementation that supports std::dynarray. However, after its recent approval, vendors should support it soon.

Danny Kalev is a certified system analyst and software engineer specializing in C++. Kalev has written several C++ textbooks and contributes C++ content regularly on various software developers' sites. He was a member of the C++ standards committee and has a master's degree in general linguistics. He’s now pursuing a PhD in linguistics. Follow Danny on Twitter.

See also:


Close

Add a little SmartBear to your life

Stay on top of your Software game with the latest developer tips, best practices and news, delivered straight to your inbox

By submitting this form, you agree to our
Terms of Use and Privacy Policy

Thanks for Subscribing

Keep an eye on your inbox for more great content.

Continue Reading