Выбрать главу

Manipulating raw storage

The raw_storage_iterator is defined in <memory> and is an output iterator. It is provided to enable algorithms to store their results in uninitialized memory. The interface is quite simple: the constructor takes an output iterator that is pointing to the raw memory (thus it is typically a pointer), and the operator= assigns an object into that raw memory. The template parameters are the type of the output iterator pointing to the raw storage and the type of object that will be stored. Here’s an example that creates Noisy objects, which print trace statements for their construction, assignment, and destruction (we’ll show the class definition later):

//: C07:RawStorageIterator.cpp

// Demonstrate the raw_storage_iterator

//{-bor}

#include <iostream>

#include <iterator>

#include <algorithm>

#include "Noisy.h"

using namespace std;

int main() {

  const int quantity = 10;

  // Create raw storage and cast to desired type:

  Noisy* np =

    reinterpret_cast<Noisy*>(

      new char[quantity * sizeof(Noisy)]);

  raw_storage_iterator<Noisy*, Noisy> rsi(np);

  for(int i = 0; i < quantity; i++)

    *rsi++ = Noisy(); // Place objects in storage

  cout << endl;

  copy(np, np + quantity,

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

  cout << endl;

  // Explicit destructor call for cleanup:

  for(int j = 0; j < quantity; j++)

    (&np[j])->~Noisy();

  // Release raw storage:

  delete reinterpret_cast<char*>(np);

} ///:~

To make the raw_storage_iterator template happy, the raw storage must be of the same type as the objects you’re creating. That’s why the pointer from the new array of char is cast to a Noisy*. The assignment operator forces the objects into the raw storage using the copy-constructor. Note that the explicit destructor call must be made for proper cleanup, and this also allows the objects to be deleted one at a time during container manipulation. In addition, the expression delete np; would be invalid anyway since the static type of a pointer in a delete expression must be the same as the type assigned to in the new expression.

The basic sequences: vector, list, deque

Sequences keep objects in whatever order you store them. They differ in the efficiency of their operations, however, so if you are going to manipulate a sequence in a particular fashion, choose the appropriate container for those types of manipulations. So far in this book we’ve been using vector as the container of choice. This is quite often the case in applications. However, when you start making more sophisticated uses of containers, it becomes important to know more about their underlying implementations and behavior so that you can make the right choices.

Basic sequence operations

Using a template, the following example shows the operations that all the basic sequences, vector, deque, and list, support.

//: C07:BasicSequenceOperations.cpp

// The operations available for all the

// basic sequence Containers.

#include <deque>

#include <iostream>

#include <list>

#include <vector>

using namespace std;

template<typename Container>

void print(Container& c, char* title = "") {

  cout << title << ':' << endl;

  if(c.empty()) {

    cout << "(empty)" << endl;

    return;

  }

  typename Container::iterator it;

  for(it = c.begin(); it != c.end(); it++)

    cout << *it << " ";

  cout << endl;

  cout << "size() " << c.size()

    << " max_size() "<< c.max_size()

    << " front() " << c.front()

    << " back() " << c.back() << endl;

}

template<typename ContainerOfInt>

void basicOps(char* s) {

  cout << "------- " << s << " -------" << endl;

  typedef ContainerOfInt Ci;

  Ci c;

  print(c, "c after default constructor");

  Ci c2(10, 1); // 10 elements, values all 1

  print(c2, "c2 after constructor(10,1)");

  int ia[] = { 1, 3, 5, 7, 9 };

  const int iasz = sizeof(ia)/sizeof(*ia);

  // Initialize with begin & end iterators:

  Ci c3(ia, ia + iasz);

  print(c3, "c3 after constructor(iter,iter)");

  Ci c4(c2); // Copy-constructor

  print(c4, "c4 after copy-constructor(c2)");

  c = c2; // Assignment operator

  print(c, "c after operator=c2");

  c.assign(10, 2); // 10 elements, values all 2

  print(c, "c after assign(10, 2)");

  // Assign with begin & end iterators:

  c.assign(ia, ia + iasz);

  print(c, "c after assign(iter, iter)");

  cout << "c using reverse iterators:" << endl;

  typename Ci::reverse_iterator rit = c.rbegin();

  while(rit != c.rend())

    cout << *rit++ << " ";

  cout << endl;

  c.resize(4);

  print(c, "c after resize(4)");

  c.push_back(47);

  print(c, "c after push_back(47)");

  c.pop_back();

  print(c, "c after pop_back()");

  typename Ci::iterator it = c.begin();

  it++; it++;

  c.insert(it, 74);

  print(c, "c after insert(it, 74)");

  it = c.begin();

  it++;

  c.insert(it, 3, 96);

  print(c, "c after insert(it, 3, 96)");

  it = c.begin();

  it++;

  c.insert(it, c3.begin(), c3.end());

  print(c, "c after insert("

    "it, c3.begin(), c3.end())");

  it = c.begin();

  it++;

  c.erase(it);

  print(c, "c after erase(it)");

  typename Ci::iterator it2 = it = c.begin();

  it++;

  it2++; it2++; it2++; it2++; it2++;

  c.erase(it, it2);

  print(c, "c after erase(it, it2)");

  c.swap(c2);

  print(c, "c after swap(c2)");