vector
, valarray
, and array
Perhaps you are wondering why C++ has three array templates: vector
, valarray
, and array
. These classes were developed by different groups for different purposes. The vector
template class is part of a system of container classes and algorithms. The vector
class supports container-oriented activities, such as sorting, insertion, rearrangement, searching, transferring data to other containers, and other manipulations. The valarray
class template, on the other hand, is oriented toward numeric computation, and it is not part of the STL. It doesn’t have push_back()
and insert()
methods, for example, but it does provide a simple, intuitive interface for many mathematical operations. Finally, array
is designed as a substitute for the built-in array type, combining the compactness and efficiency of that type with a better, safer interface. Being of fixed size, array
doesn’t support push_back()
and insert()
, but it does offer several other STL methods. These include begin(
), end()
, rbegin()
, and rend()
, making it easy to apply STL algorithms to array
objects.
Suppose, for example, that you have these declarations:
vector<double> ved1(10), ved2(10), ved3(10);
array<double, 10> vod1, vod2, vod3;
valarray<double> vad1(10), vad2(10), vad3(10);
Furthermore, assume that ved1
, ved2
, vod1
, vod2
, vad1
, and vad2
all acquire suitable values. Suppose you want to assign the sum of the first elements of two arrays to the first element of a third array, and so on. With the vector
class, you would do this:
transform(ved1.begin(), ved1.end(), ved2.begin(), ved3.begin(),
plus<double>());
You can do the same with the array
class:
transform(vod1.begin(), vod1.end(), vod2.begin(), vod3.begin(),
plus<double>());
However, the valarray
class overloads all the arithmetic operators to work with valarray
objects, so you would use this:
vad3 = vad1 + vad2; // + overloaded
Similarly, the following would result in each element of vad3
being the product of the corresponding elements in vad1
and vad2
:
vad3 = vad1 * vad2; // * overloaded
Suppose you want to replace every value in an array with that value multiplied by 2.5. The STL approach is this:
transform(ved3.begin(), ved3.end(), ved3.begin(),
bind1st(multiplies<double>(), 2.5));
The valarray
class overloads multiplying a valarray
object by a single value, and it also overloads the various computed assignment operators, so you could use either of the following:
vad3 = 2.5 * vad3; // * overloaded
vad3 *= 2.5; // *= overloaded
Suppose you want to take the natural logarithm of every element of one array and store the result in the corresponding element of a second array. The STL approach is this:
transform(ved1.begin(), ved1.end(), ved3.begin(),
log);
The valarray
class overloads the usual math functions to take a valarray
argument and to return a valarray
object, so you can use this:
vad3 = log(vad1); // log() overloaded
Or you could use the apply()
method, which also works for non-overloaded functions:
vad3 = vad1.apply(log);
The apply()
method doesn’t alter the invoking object; instead, it returns a new object that contains the resulting values.
The simplicity of the valarray
interface is even more apparent when you do a multistep calculation:
vad3 = 10.0* ((vad1 + vad2) / 2.0 + vad1 * cos(vad2));
The vector
-STL version is left as an exercise for the motivated reader.
The valarray
class also provides a sum()
method that sums the contents of a valarray
object, a size()
method that returns the number of elements, a max()
method that returns the largest value in an object, and a min()
method that returns the smallest value.
As you can see, valarray
has a clear notational advantage over vector
for mathematical operations, but it is also much less versatile. The valarray
class does have a resize()
method, but there’s no automatic resizing of the sort you get when you use the vector push_back()
method. There are no methods for inserting values, searching, sorting, and the like. In short, the valarray
class is more limited than the vector
class, but its narrower focus allows a much simpler interface.
Does the simpler interface that valarray
provides translate to better performance? In most cases, no. The simple notation is typically implemented with the same sort of loops you would use with ordinary arrays. However, some hardware designs allow vector operations in which the values in an array are loaded simultaneous into an array of registers and then processed simultaneously. In principle, valarray
operations could be implemented to take advantage of such designs.
Can you use the STL with valarray
objects? Answering this question provides a quick review of some STL principles. Suppose you have a valarray<double>
object that has 10 elements:
valarray<double> vad(10);
After the array has been filled with numbers, can you, say, use the STL sort function on it? The valarray
class doesn’t have begin()
and end()
methods, so you can’t use them as the range arguments:
sort(vad.begin(), vad.end()); // NO, no begin(), end()
Also vad
is an object, not a pointer, so you can’t imitate ordinary array usage and provide vad
and vad + 10
, as the following code attempts to do:
sort(vad, vad + 10); // NO, vad an object, not an address
You can use the address operator:
sort(&vad[0], &vad[10]); // maybe?
But the behavior of using a subscript one past the end is undefined for valarray
. This doesn’t necessarily mean using &vad[10
] won’t work.(In fact, it did work for all six compilers used to test this code.) But it does mean that it might not work. For the code to fail, you probably would need a very unlikely circumstance, such as the array being butted against the end of the block of memory set aside for the heap. But, if a $385-million mission depended on your code, you might not want to risk that failure.
C++11 remedies the situation by providing begin()
and end()
template functions that take a valarray
object as an argument. So you would use begin(vad)
instead of vad.begin()
. These functions return values that are compatible with STL range requirements:
sort(begin(vad), end(vad)); // C++11 fix!
Listing 16.20 illustrates some of the relative strengths of the vector
and valarray
classes. It uses push_back()
and the automatic sizing feature of vector
to collect data. Then after sorting the numbers, the program copies them from the vector
object to a valarray
object of the same size and does a few math operations.
// valvect.cpp -- comparing vector and valarray
#include <iostream>
#include <valarray>
#include <vector>
#include <algorithm>
int main()
{
using namespace std;
vector<double> data;
double temp;
cout << "Enter numbers (<=0 to quit):
";
while (cin >> temp && temp > 0)
data.push_back(temp);
sort(data.begin(), data.end());
int size = data.size();
valarray<double> numbers(size);
int i;
for (i = 0; i < size; i++)
numbers[i] = data[i];
valarray<double> sq_rts(size);
sq_rts = sqrt(numbers);
valarray<double> results(size);
results = numbers + 2.0 * sq_rts;
cout.setf(ios_base::fixed);
cout.precision(4);
for (i = 0; i < size; i++)
{
cout.width(8);
cout << numbers[i] << ": ";
cout.width(8);
cout << results[i] << endl;
}
cout << "done
";
return 0;
}
Here is a sample run of the program in Listing 16.20:
Enter numbers (<=0 to quit):
3.3 1.8 5.2 10 14.4 21.6 26.9 0
1.8000: 4.4833
3.3000: 6.9332
5.2000: 9.7607
10.0000: 16.3246
14.4000: 21.9895
21.6000: 30.8952
26.9000: 37.2730
done
The valarray
class has many features besides the ones discussed so far. For example, if numbers
is a valarray<double>
object, the following statement creates an array of bool
values, with vbool[i]
set to the value of numbers[i] > 9
—that is, to true
or false
:
valarray<bool> vbool = numbers > 9;
There are extended versions of subscripting. Let’s look at one—the slice
class. A slice
class object can be used as an array index, in which case it represents, in general, not just one value but a subset of values. A slice
object is initialized to three integer values: the start, the number, and the stride. The start indicates the index of the first element to be selected, the number indicates the number of elements to be selected, and the stride represents the spacing between elements. For example, the object constructed by slice(1,4,3)
means select the four elements whose indexes are 1, 4, 7, and 10. That is, start with the start element, add the stride to get the next element, and so on until four elements are selected. If, say, varint
is a vararray<int>
object, then the following statement would set elements 1, 4, 7, and 10 to the value 10
:
varint[slice(1,4,3)] = 10; // set selected elements to 10
This special subscripting facility allows you to use a one-dimensional valarray
object to represent two-dimensional data. For example, suppose you want to represent an array with 4 rows and 3 columns. You can store the information in a 12-element valarray
object. Then a slice(0,3,1)
object used as a subscript would represent elements 0, 1, and 2—that is, the first row. Similarly, a slice(0,4,3)
subscript would represent elements 0, 3, 6, and 9—that is, the first column. Listing 16.21 illustrates some features of slice
.
// vslice.cpp -- using valarray slices
#include <iostream>
#include <valarray>
#include <cstdlib>
const int SIZE = 12;
typedef std::valarray<int> vint; // simplify declarations
void show(const vint & v, int cols);
int main()
{
using std::slice; // from <valarray>
using std::cout;
vint valint(SIZE); // think of as 4 rows of 3
int i;
for (i = 0; i < SIZE; ++i)
valint[i] = std::rand() % 10;
cout << "Original array:
";
show(valint, 3); // show in 3 columns
vint vcol(valint[slice(1,4,3)]); // extract 2nd column
cout << "Second column:
";
show(vcol, 1); // show in 1 column
vint vrow(valint[slice(3,3,1)]); // extract 2nd row
cout << "Second row:
";
show(vrow, 3);
valint[slice(2,4,3)] = 10; // assign to 2nd column
cout << "Set last column to 10:
";
show(valint, 3);
cout << "Set first column to sum of next two:
";
// + not defined for slices, so convert to valarray<int>
valint[slice(0,4,3)] = vint(valint[slice(1,4,3)])
+ vint(valint[slice(2,4,3)]);
show(valint, 3);
return 0;
}
void show(const vint & v, int cols)
{
using std::cout;
using std::endl;
int lim = v.size();
for (int i = 0; i < lim; ++i)
{
cout.width(3);
cout << v[i];
if (i % cols == cols - 1)
cout << endl;
else
cout << ' ';
}
if (lim % cols != 0)
cout << endl;
}
The +
operator is defined for valarray
objects, such as valint
, and it’s defined for a single int
element, such as valint[1]
. But as the code in Listing 16.21 notes, the +
operator isn’t defined for slice
-subscripted valarray
units, such as valint[slice(1,4,3)]
. Therefore, the program constructs full objects from the slices to enable addition:
vint(valint[slice(1,4,3)]) // calls a slice-based constructor
The valarray
class provides constructors just for this purpose.
Here is a sample run of the program in Listing 16.21:
Original array:
0 3 3
2 9 0
8 2 6
6 9 1
Second column:
3
9
2
9
Second row:
2 9 0
Set last column to 10:
0 3 10
2 9 10
8 2 10
6 9 10
Set first column to sum of next two:
13 3 10
19 9 10
12 2 10
19 9 10
Because values are set using rand()
, different implementations of rand()
will result in different values.
There’s more, including the gslice
class to represent multidimensional slices, but this should be enough to give you a sense of what valarray
is about.
3.142.133.180