• No results found

Converting between sequences

In document Thinking in C++ (Page 182-185)

Knowing now what you do about the inefficiency of adding things to vector because of storage reallocation, you may expect dramatic differences between the two. However, on a 1.7 Megabyte text file one compiler’s program produced the following (measured in

platform/compiler specific clock ticks, not seconds):

Read into vector: 8350 Read into deque: 7690 Indexing vector: 2360 Indexing deqeue: 2480 Iterating vector: 2470 Iterating deqeue: 2410

A different compiler and platform roughly agreed with this. It’s not so dramatic, is it? This points out some important issues:

1. We (programmers) are typically very bad at guessing where inefficiencies occur in our programs.

2. Efficiency comes from a combination of effects – here, reading the lines in and converting them to strings may dominate over the cost of the vector vs. deque.

3. The string class is probably fairly well-designed in terms of efficiency.

Of course, this doesn’t mean you shouldn’t use a deque rather than a vector when you know that an uncertain number of objects will be pushed onto the end of the container. On the contrary, you should – when you’re tuning for performance. But you should also be aware that performance issues are usually not where you think they are, and the only way to know for sure where your bottlenecks are is by testing. Later in this chapter there will be a more

“pure” comparison of performance between vector, deque and list.

Converting between sequences

Sometimes you need the behavior or efficiency of one kind of container for one part of your program, and a different container’s behavior or efficiency in another part of the program. For example, you may need the efficiency of a deque when adding objects to the container but the efficiency of a vector when indexing them. Each of the basic sequence containers (vector, deque and list) has a two-iterator constructor (indicating the beginning and ending of the sequence to read from when creating a new object) and an assign( ) member function to read into an existing container, so you can easily move objects from one sequence container to another.

The following example reads objects into a deque and then converts to a vector:

//: C04:DequeConversion.cpp

// Reading into a Deque, converting to a vector

#include "Noisy.h"

#include <deque>

#include <vector>

#include <iostream>

#include <algorithm>

#include <cstdlib>

using namespace std;

int main(int argc, char* argv[]) { int size = 25;

if(argc >= 2) size = atoi(argv[1]);

deque<Noisy> d;

generate_n(back_inserter(d), size, NoisyGen());

cout << "\n Converting to a vector(1)" << endl;

vector<Noisy> v1(d.begin(), d.end());

cout << "\n Converting to a vector(2)" << endl;

vector<Noisy> v2;

v2.reserve(d.size());

v2.assign(d.begin(), d.end());

cout << "\n Cleanup" << endl;

} ///:~

You can try various sizes, but you should see that it makes no difference – the objects are simply copy-constructed into the new vectors. What’s interesting is that v1 does not cause multiple allocations while building the vector, no matter how many elements you use. You might initially think that you must follow the process used for v2 and preallocate the storage to prevent messy reallocations, but the constructor used for v1 determines the memory need ahead of time so this is unnecessary.

Cost of overflowing allocated storage

It’s illuminating to see what happens with a deque when it overflows a block of storage, in contrast with VectorOverflow.cpp:

//: C04:DequeOverflow.cpp

// A deque is much more efficient than a vector // when pushing back a lot of elements, since it // doesn't require copying and destroying.

#include "Noisy.h"

#include <deque>

#include <cstdlib>

using namespace std;

int main(int argc, char* argv[]) { int size = 1000;

if(argc >= 2) size = atoi(argv[1]);

deque<Noisy> dn;

Noisy n;

for(int i = 0; i < size; i++) dn.push_back(n);

cout << "\n cleaning up \n";

} ///:~

Here you will never see any destructors before the words “cleaning up” appear. Since the deque allocates all its storage in blocks instead of a contiguous array like vector, it never needs to move existing storage (thus no additional copy-constructions and destructions occur).

It simply allocates a new block. For the same reason, the deque can just as efficiently add elements to the beginning of the sequence, since if it runs out of storage it (again) just allocates a new block for the beginning. Insertions in the middle of a deque, however, could be even messier than for vector (but not as costly).

Because a deque never moves its storage, a held iterator never becomes invalid when you add new things to either end of a deque, as it was demonstrated to do with vector (in

VectorCoreDump.cpp). However, it’s still possible (albeit harder) to do bad things:

//: C04:DequeCoreDump.cpp

// How to break a program using a deque

#include <queue>

#include <iostream>

using namespace std;

int main() {

deque<int> di(100, 0);

// No problem iterating from beginning to end, // even though it spans multiple blocks:

copy(di.begin(), di.end(),

ostream_iterator<int>(cout, " "));

deque<int>::iterator i = // In the middle:

di.begin() + di.size() / 2;;

// Walk the iterator forward as you perform // a lot of insertions in the middle:

for(int j = 0; j < 1000; j++) { cout << j << endl;

di.insert(i++, 1); // Eventually breaks }

} ///:~

Of course, there are two things here that you wouldn’t normally do with a deque: first, elements are inserted in the middle, which deque allows but isn’t designed for. Second, calling insert( ) repeatedly with the same iterator would not ordinarily cause an access violation, but the iterator is walked forward after each insertion. I’m guessing it eventually walks off the end of a block, but I’m not sure what actually causes the problem.

If you stick to what deque is best at – insertions and removals from either end, reasonably rapid traversals and fairly fast random-access using operator[ ] – you’ll be in good shape.

In document Thinking in C++ (Page 182-185)