The C++ language is often used in scientific and engineering work to perform long and difficult operations on arrays of numbers. While the language itself provides the flexibility and efficiency needed for these kind of calculations, the code can become very complex. Naturally, we can use the object-oriented features of the language to encapsulate this complexity, but classes designed to simplify numeric programming without sacrificing performance are notoriously difficult to code correctly. Fortunately, the C++ Standard Library now provides some relief in the form of the valarray class template.
This class template provides the necessary efficiency in the form of a single-dimensional array. Class valarray can be used to perform calculations directly on arrays in a single dimension, and to represent higher order arrays with a little more effort. An extended set of subscripting operators provides the basis for building matrices and other more sophisticated classes from the relatively simple and lean valarray.
Since efficiency is such an important concern in numeric programming, the valarray class addresses performance in several ways.
First of all, valarray is designed to give compilers maximum latitude in optimizing operations within the class. This is achieved primarily by avoiding any aliasing of elements within a valarray. This means that every element in a given valarray is located at its own unique memory address, exactly as it would be in an ordinary array. While this prevents a valarray implementation from using certain kinds of optimizations, such as aliasing all elements with default values to a single stored value, that loss is more than compensated by a competent optimizer's ability to streamline operations on the array.
Secondly, the valarray class uses internal optimization templates to ensure that the most efficient method is used to copy data whenever possible. This optimization is possible in part because of the restriction on aliasing.
Lastly, the valarray class imposes certain requirements on any type used with it. Most of these restrictions are not related to performance; they are simply necessary to allow generalized numeric operations. But one in particular, initialization semantics, exists at least in part to allow optimizations. A summary of these restrictions follows.
A valarray can be instantiated only on a type T meeting the following requirements:
A type T must not be an abstract class.
A type T cannot be a reference type; that is, std::valarray<int&> is not allowed.
A type T must not be const or volatile qualified; in other words, std::valarray<const int> is not allowed.
A type T must not overload unary operator&.
A type T must not throw exceptions.
If a type T is a class it must have a public default constructor, a public copy constructor with the signature T::T(const T&), and a public destructor.
If a type T is a class, it must have a public assignment operator with a signature matching one of the following: T& T::operator=(const T&) or T& T::operator=(T).
If a type T is a class, then its default constructor, copy constructor, and assignment operator must behave in such a way that there is no difference between default construction followed by assignment, and copy construction. Additionally, destruction of an object followed by copy construction must be equivalent to assignment.
Here is a summary of the necessary relationship between copy construction and assignment, where T is the type, t and v are instances of the type, and p is a pointer to an instance of the type:
T t(), t = v | is semantically equivalent to: | T t(v) |
new (p) T(), *p = v | is semantically equivalent to: | new (p) T(v) |
p->~T(), new (p) T(v) | is semantically equivalent to: | *p = v |
This summary demonstrates that there is nothing subtle and clever happening in the process of copy construction or assignment. This consistency is particularly important for portability, since any implementation of valarray can expect that these operations have the described equivalency even if a particular implementation may not care.
All the built-in numeric types, like int, long, float, and so on, clearly meet the type requirements for valarray. Here is an example of a minimalist class that does, too. This class is simple enough that the compiler generated constructors, destructor, and assignment operator would actually suffice. As you can see, there is nothing very special needed here. Rather, it is the absence of something special that the requirements guard against.
class Num { int val_; public: Num() : val_(0) {} // public default constructor Num(const Num& n):val_(n.val()) {} // public copy constructor ~Num() {} // public destructor Num& operator=(const Num& n) // public assignment { val-_ = n.val(); return *this; } int val() const { return val_; } int val(int v) { int tmp = val_; val_ = v; return tmp; } };
Now let's look at a small variation on the Num class that does not meet the type requirements for class valarray. Depending on how this class is used, it may or may not be a problem in a valarray, but it most definitely violates the requirements since default construction followed by assignment may result in a different state than copy construction.
class Num { bool assigned_; int val_; public: Num(): val_(0), assigned_(false) {} // public default constructor Num(const Num& n) // public copy constructor : val_(n.val()), assigned_(n.assigned()) {} ~Num() {} // public destructor Num& operator=(const Num& n) // public assignment { val_ = n.val(); assigned_ = true; // Whoops, violates rule 8 if // n.assigned() == false. return *this; } int val() const { return val_; } int val(int v) { int tmp = val_; val_ = v; return tmp; } bool assigned() const { return assigned; } };
It is important to note that operations not defined for a particular type are not available for a valarray of that type. For instance, if a type does not have ordering operations, as is the case with the standard complex class template, then those ordering operands are not available in a valarray of that type.
Another important feature is that, along with the valarray class, the valarray header defines several auxiliary classes to support extended subset operations. These are slice, gslice, slice_array, gslice_array, indirect_array, and mask_array, which are explained in Section 22.4.2 and its subsections. For now we'll just note that slice and gslice are used to define the parameters of a BLAS-like slice, or a generalized slice into an array. The remainder of these auxiliary classes define the types returned by subset operations. None of these can be instantiated directly by a program since they lack public constructors.
Programs that use valarrays must include the valarray header file:
#include <valarray>