Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:
Для анализа правильности программы наиболее полезными являются базовая и жесткая гарантии. Принцип RAII играет существенную роль для реализации простого и эффективного кода, написанного в соответствии с этими идеями. Более подробную информацию можно найти в приложении Д книги Язык программирования С++.
19.5.4. Класс auto_ptr
Итак, функции, такие как
make_vec
,
try ... catch
по-прежнему выглядит ужасно. Решение очевидно: нужно как-то применить принцип RAII; иначе говоря, необходимо предусмотреть объект, который будет владеть объектом класса vector<int>
и сможет его удалить, если возникнет исключение. В заголовке <memory>
стандартной библиотеки содержится класс auto_ptr
, предназначенный именно для этого.
vector<int>* make_vec // создает заполненный вектор
{
auto_ptr< vector<int> > p(new vector<int>); // выделяет свободную
// память
// ...заполняем вектор данными;
// возможна генерация исключения...
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;
}
alloc.construct(&p[i],elem[i])
может генерировать исключение. Следовательно, указатель p
— это пример проблемы, о которой мы предупреждали в разделе 19.5.1. Ой! Можно было бы применить класс auto_ptr
. А еще лучше — вернуться назад и понять, что память для вектора — это ресурс; иначе говоря, мы можем определить класс vector_base
для выражения фундаментальной концепции, которую используем все время. Эта концепция изображена на следующем рисунке, содержащем три элемента, определяющих использование памяти, предназначенной для вектора: Добавив для полноты картины распределитель памяти, получим следующий код:
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]); // копируем
for (int i=0; i<sz; ++i)
alloc.destroy(&elem[i]); // освобождаем память
swap< vector_base<T,A> >(*this,b); // меняем представления
Поделиться с друзьями:
- Telegram
- Viber
- Skype
- ВКонтакте