На этом шаге мы рассмотрим особенности передачи прав владения объектом.
Семантика auto_ptr всегда предполагает владение объектом, поэтому не используйте auto_ptr в параметрах или возвращаемом значении, если вы не собираетесь передавать право владения. Для примера рассмотрим наивную реализацию функции вывода объекта, на который ссылается auto_ptr. Попытка применить такую функцию на практике закончится катастрофой.
// ПЛОХОЙ ПРИМЕР template <class T> void bad_print (std::auto_ptr <T> p) // p становится владельцем // переданного аргумента { // Указывает ли р на объект? if (p.get() == NULL) { std::cout << "NULL"; } else { std::cout << *p; } } // Ошибка - обьект, на который ссылается р, // удаляется при выходе из функции.
Каждый раз, когда этой реализации bad_print передается аргумент типа auto_ptr, принадлежащий ему объект (если он есть) уничтожается. Дело в том, что вместе со значением аргумента параметру р передается право владения объектом auto_ptr, а объект, принадлежащий р, удаляется при выходе из функции. Вряд ли программист учел такой поворот событий, поэтому, скорее всего, результатом будет фатальная ошибка времени выполнения:
std::auto_ptr <int> p(new int); *p = 42; // Изменение значения, на которое ссылается р bad_print(p); // Удаление данных, на которые ссылается р *р = 18; // ОШИБКА ВРЕМЕНИ ВЫПОЛНЕНИЯ
Может, стоит передавать auto_ptr по ссылке? Однако передача auto_ptr по ссылке противоречит концепции владения. Нельзя быть полностью уверенным в том, что функция, получающая auto_ptr по ссылке, передаст (или не передаст) право владения. Передача auto_ptr по ссылке - очень плохое решение, никогда не используйте его в своих программах.
В соответствии с концепцией auto_ptr право владения может передаваться функции при помощи константной ссылки. Но такое решение очень опасно, поскольку программисты обычно полагают, что объект, передаваемый по константной ссылке, остается неизменным. К счастью, на поздней стадии проектирования было принято решение, которое сделало применение auto_ptr менее рискованным. Благодаря каким-то ухищрениям реализации передача права владения с константными ссылками стала невозможной. Более того, право владения для констант auto_ptr вообще не изменяется:
const std::auto_ptr <int> p(new int); *p = 42; // Изменение значения, на которое ссылается р bad_print(p); // ОШИБКА КОМПИЛЯЦИИ *р = 18; // ОК
Такое решение повышает надежность auto_ptr. Многие интерфейсы используют константные ссылки для получения данных, которые проходят внутреннее копирование. Собственно говоря, все контейнерные классы стандартной библиотеки C++ ведут себя подобным образом:
template <class T> void container::insert (const T& value) { . . . . x = value; // Внутреннее копирование . . . . }
Если бы такое присваивание было возможно для auto_ptr, то операция присваивания передала бы право владения контейнеру. Но из-за реальной архитектуры auto_ptr этот вызов приводит к ошибке на стадии компиляции:
контейнер <std:auto_ptr<int> > с; const std::auto_ptr<int> p(new int); . . . . c.insert(p): // ОШИБКА . . . .
В конечном счете константные объекты auto_ptr снижают вероятность непредвиденной передачи права владения. Каждый раз, когда объект передается через auto_ptr, вы можете воспользоваться константным экземпляром auto_ptr и тем самым завершить цепочку передачи.
Константность не означает неизменности объекта, принадлежащего auto_ptr. Вы не сможете изменить право владения для константного экземпляра auto_ptr, однако ничто не мешает изменить значение, на которое он ссылается. Пример:
std::auto_ptr<int> f() { const std::auto_ptr<int> p(new int); // Право владения не передается std::auto_ptr<int> q(new int); // Право владения передается *p = 42; // OK: изменение значения, на которое ссылается р bad_print(p); // ОШИБКА КОМПИЛЯЦИИ *р = *q; // OK; изменение значения, на которое ссылается р р = q; // ОШИБКА КОМПИЛЯЦИИ return p; // ОШИБКА КОМПИЛЯЦИИ }
Если const auto_ptr передается или возвращается в аргументе, любая попытка присвоить новый объект приводит к ошибке компиляции. В отношении константности const auto_ptr ведет себя как константный указатель (Т* const p), а не как указатель на константу (const T* р), хотя синтаксис вроде бы говорит об обратном.
На следующем шаге мы рассмотрим использование этих объектов как переменных классов.