На этом шаге мы познакомимся с классом auto_ptr.
НАчиная с этого шага мы начнем расматривать тип auto_ptr. Стандартная библиотека C++ предоставляет его как своего рода "умный указатель", помогающий предотвратить утечки ресурсов при исключениях. Обратите внимание на слова "своего рода". Они добавлены, потому что на практике применяется несколько разновидностей умных указателей. Класс auto_ptr полезен только при решении определенного круга проблем. За его пределами тип auto_ptr не поможет.
Функции часто работают по следующей схеме.
Если полученные ресурсы связаны с локальными объектами, они автоматически освобождаются при выходе из функции в результате вызова деструкторов локальных объектов. Но если ресурсы не связаны с объектом, оии должны освобождаться явно. Как правило, такие операции с ресурсами выполняются с применением указателей.
Характерный пример работы с ресурсами через указатели - создание и уничтожение объектов операторами new и delete:
void f() { ClassA* ptr = new ClassA; // Создание объекта . . . . // Выполнение операций delete ptr; // Уничтожение обьекта }
При использовании подобных схем нередко возникают проблемы. Первая и наиболее очевидная проблема заключается в том, что программист может забыть об удалении объекта (особенно если внутри функции присутствуют команды return). Но существует и другая, менее очевидная опасность: во время работы функции может произойти исключение, что приведет к немедленному выходу из функции без выполнения оператора delete, находящегося в конце тела функции. В результате возникает утечка памяти или, в общем случае, - ресурсов. Для ее предотвращения обычно приходится перехватывать все возможные исключения. Пример:
void f() { ClassA* ptr = new ClassA: // Создание объекта try { . . . . // Работа с объектом } catch (...){ // Для произвольного исключения: delete ptr; // - освободить ресурс throw; // - перезапустить исключение } delete ptr; // Нормальное освобождение ресурса }
Освобождение объекта при возникновении исключений приводит к усложнению программы и появлению лишнего кода. Если по этой схеме обрабатывается не один, а два объекта или имеется несколько секций catch, программа становится еще запутаннее. В подобных решениях - сложных и чреватых ошибками - проявляется плохой стиль программирования.
В данной ситуации нужен умный указатель, освобождающий данные, на которые он ссылается, при уничтожении самого указателя. Более того, поскольку такой указатель является локальной переменной, он будет уничтожаться автоматически при выходе из функции независимо от причины выхода - нормального завершения или исключения. Класс auto_ptr проектировался именно для этих целей.
Указатель auto_ptr является владельцем объекта, иа который он ссылается. В результате уничтожение указателя автоматически приводит к уничтожению объекта. Для работы auto_ptr необходимо, чтобы управляемый объект имел только одного владельца.
Ниже приведен предыдущий пример, переработанный с применением auto_ptr:
// Заголовочный файл для auto_ptr #include <memory> void f() { // Создание и инициализация auto_ptr std::auto_ptr<ClassA> ptr (new ClassA): . . . . // Работа с указателем }
Команда delete и секция catch стали лишними. Интерфейс указателя auto_ptr почти не отличается от интерфейса обычного указателя; оператор * производит разыменование объекта, на который ссылается указатель, а оператор -> предоставляет доступ к членам класса или структуры. Математические операции с указателями (такие, как ++) для auto_ptr не определены. Впрочем, это скорее достоинство, нежели недостаток, потому что вычисления с указателями слишком часто приводят к неприятностям.
Учтите, что тип auto_ptr не позволяет инициализировать объект обычным указателем в конструкции присваивания. Инициализация auto_ptr должна производиться напрямую по значению:
std::auto_ptr<ClassA> ptr1(new ClassA); // OK std::auto_ptr<ClassA> ptr2 = new ClassA; // ОШИБКА
X x;
Y y(х); // Явное преобразование
X х;
Y y = х; // Неявное преобразование
На следующем шаге мы продолжим знакомство с этим классом.