Литмир - Электронная Библиотека
A
A

  // возможна генерация исключения...

  return p.release();  // возвращаем указатель,

                       // которым владеет объект p

}

Объект класса

auto_ptr
просто владеет указателем в функции. Он немедленно инициализируется указателем, созданным с помощью оператора
new
. Теперь мы можем применять к объектам класса
auto_ptr
операторы
–>
и
*
как к обычному указателю (например,
p–> at(2)
или
(*p).at(2)
), так что объект класса
auto_ptr
можно считать разновидностью указателя. Однако не спешите копировать класс
auto_ptr
, не прочитав соответствующей документации; семантика этого класса отличается от семантики любого типа, который мы до сих пор встречали. Функция
release()
вынуждает объект класса
auto_ptr
вернуть обычный указатель обратно, так что мы можем вернуть этот указатель, а объект класса
auto_ptr
не сможет уничтожить объект, на который установлен возвращаемый указатель. Если вам не терпится использовать класс
auto_ptr
в более интересных ситуациях (например, скопировать его объект), постарайтесь преодолеть соблазн. Класс
auto_ptr
предназначен для того, чтобы владеть указателем и гарантировать уничтожение объекта при выходе из области видимости. Иное использование этого класса требует незаурядного мастерства. Класс
auto_ptr
представляет собой очень специализированное средство, обеспечивающее простую и эффективную реализацию таких функций, как
make_vec()
. В частности, класс
auto_ptr
позволяет нам повторить наш совет: с подозрением относитесь к явному использованию блоков
try
; большинство из них вполне можно заменить, используя одно из применений принципа RAII. 

19.5.5. Принцип RAII для класса vector

Даже использование интеллектуальных указателей, таких как

auto_ptr
, может показаться недостаточно безопасным. Как убедиться, что мы выявили все указатели, требующие защиты? Как убедиться, что мы освободили все указатели, которые не должны были уничтожаться в конце области видимости? Рассмотрим функцию
reserve()
из раздела 19.3.5.

template<class T, class A>

void vector<T,A>::reserve(int newalloc)

{

  if (newalloc<=space) return;     // размер никогда не уменьшается

  T* p = alloc.allocate(newalloc); // выделяем новую память

  for (int i=0; i<sz; ++i) alloc.construct(&p[i],elem[i]);

                                   // копируем

  for (int i=0; i<sz; ++i) alloc.destroy(&elem[i]); // уничтожаем

  alloc.deallocate(elem,space);    // освобождаем старую память

  elem = p;

  space = newalloc;

}

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Обратите внимание на то, что операция копирования старого элемента
alloc.construct(&p[i],elem[i])
может генерировать исключение. Следовательно, указатель
p
— это пример проблемы, о которой мы предупреждали в разделе 19.5.1. Ой! Можно было бы применить класс
auto_ptr
. А еще лучше — вернуться назад и понять, что память для вектора — это ресурс; иначе говоря, мы можем определить класс
vector_base
для выражения фундаментальной концепции, которую используем все время. Эта концепция изображена на следующем рисунке, содержащем три элемента, определяющих использование памяти, предназначенной для вектора:

Программирование. Принципы и практика использования C++ Исправленное издание - _214.png

Добавив для полноты картины распределитель памяти, получим следующий код:

template<class T, class A>

struct vector_base {

  A alloc;    // распределитель памяти

  T* elem;    // начало распределения

  int sz;     // количество элементов

  int space;  // размер выделенной памяти

  vector_base(const A& a, int n)

  :alloc(a), elem(a.allocate(n)), sz(n), space(n) { }

  ~vector_base() { alloc.deallocate(elem,space); }

};

Обратите внимание на то, что класс

vector_base
работает с памятью, а не с типизированными объектами. Нашу реализацию класса
vector
можно использовать для владения объектом, имеющим желаемый тип элемента. По существу, класс
vector
— это просто удобный интерфейс для класса
vector_base
.

template<class T, class A = allocator<T> >

class vector:private vector_base<T,A> {

public:

  // ...

};

Теперь можно переписать функцию

reserve()
, сделав ее более простой и правильной.

template<class T, class A>

void vector<T,A>::reserve(int newalloc)

{

  if (newalloc<=space) return;  // размер никогда не уменьшается

  vector_base<T,A> b(alloc,newalloc);   // выделяем новую память

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

  alloc.construct(&b.elem[i], elem[i]); // копируем

267
{"b":"847443","o":1}