Программирование >>  Оптимизация возвращаемого значения 

1 ... 53 54 55 [ 56 ] 57 58 59 ... 96


Обычно умное поведение, обеспечиваемое интеллектуальным указателем, является необходимым компонентом разработки, поэтому разрешение клиентам использовать обычные указатели, вы, возможно, спровоцируете катастрофу. Например, если интеллектуальный указатель DBPtr реализует стратегию подсчета ссылок, описанную в правиле 29, и вы позволите клиентам напрямую работать с простыми указателями, это почти неизбежно вызовет ошибки, которые нарушат структуры данных, отвечающие за подсчет ссылок.

Даже если создать оператор неявного преобразования из интеллектуального указателя в обычный, интеллектуальные указатели и обычные не будут равнозначны. Это связано с тем, что преобразование из интеллектуального указателя в обычный определено пользователем, а компилятору запрещено применять более одного такого преобразования одновременно. Например, предположим, что имеется класс, представляющий всех клиентов, получивших доступ к определенному кортежу:

class TupleAccessors { public:

TupleAccessors(const Tuple *pt); Указатель pt определяет ... заданный кортеж.

Как обычно, конструктор с одним аргументом TupleAccessor также служит оператором преобразования типа из Tuple* в TupleAccessors (см. правило 5). Рассмотрим теперь функцию для слияния данных из двух объектов TupleAccessors:

TupleAccessors merge(const TupleAccessors& tal,

const TupleAccessorsSt ta2) ;

Поскольку указатель Tuple* может быть неявно преобразован в объект TupleAccessors, вызов функции merge с двумя обычными указателями Tuple* компилируется без каких-либо проблем:

Tuple *ptl, *pt2;

merge(ptl, pt2); Нормально, оба указателя

преобразуются в объекты TupleAccessors.

Соответствующий же вызов для интеллектуальных указателей DBPtr-<Tuple> компилироваться не будет:

DBPtr<Tuple> ptl, pt2;

merge(ptl, pt2); Ошибка! Невозможно

преобразовать ptl и pt2 в объект TupleAccessors.

Это связано с тем, что преобразование DBPtr<Tuple> в объект TupleAccessors требует вызова двух определенных пользователем преобразований (одно из



DBPtr<Tuple> В Tuple* и одно из Tuple* в TupleAccessors), а такие последовательности преобразований в языке запрещены.

Классы интеллектуальных указателей, в которых есть функции неявного преобразования в обычный указатель, приводят к очень неприятной ошибке. Рассмотрим код:

DBPtr<Tuple> pt = new Tuple; delete pt;

Такой код не должен был бы компилироваться. В конце концов, объект pt не является указателем, и его нельзя удалить. Ведь можно удалять только указатели, не так ли?

Да, это так. Но вы должны помнить из правила 5, что компиляторы используют неявное преобразование типов для того, чтобы по возможности сделать вызовы функций успешными, а из правила 8 - что использование оператора delete приводит к вызову двух функций (деструктора и operator delete). Компиляторы пытаются сделать вызов данных функций успешным, поэтому в операторе delete они неявно преобразуют pt в Tuple*, а затем удаляют его. Это почти наверняка приведет к остановке вашей программы.

Если интеллектуальный указатель pt является владельцем объекта, на который он указывает, то этот объект удаляется дважды, один раз в точке вызова оператора delete, а второй - при вызове деструктора pt. Если объектом не владеет pt, то им владеет кто-то другой. Возможно, это был тот, кто удалил pt, и тогда все в порядке. В противном случае настоящий владелец скорее всего попытается позже снова удалить объект. Первый и последний варианты означают, что объект удаляется дважды, а удаление объекта более одного раза приводит к неопределенному поведению.

Эта ошибка очень опасна, так как основная идея, лежащая в основе интеллектуальных указателей - сделать их максимально похожими на обычные указатели. Чем ближе вы приближаетесь к этому идеалу, тем более вероятно, что клиенты забудут о том, что они используют интеллектуальные указатели. И если это произойдет, кто сможет обвинить их в вызове delete после new, ведь тем самым они стремятся (по их мнению) избежать утечек ресурсов?

Итог всего вышесказанного достаточно прост: не создавайте операторов неявного преобразования в обычные указатели без настоятельной необходимости.

Интеллектуальные указатели и преобразования типов при наследовании

Предположим, что имеется открытая иерархия наследования, соответствующая музыкальным носителям для бытовой аппаратуры (см. рис. 5.4).

class MusicProduct { public:

MusicProduct(const strings title); virtual void playO const = 0;




Рис. 5.4

Предположим далее, что имеется функция, выводящая название объекта MusicProduct, а затем проигрывающая соответствующий носитель:

void displayAndPlay (const MusicProduct* pmp, int numTimes) {

for (int i = 1; i <= numTimes; ++i) { pmp->displayTitle(); pmp->play();

Такую функцию можно использовать следующим образом:

Cassette *funMusic = new Cassette( Alapalooza ); CD *nightmareMusic = new CD ( Disco Hits of the 70s ) ; displayAndPlay(funMusic, 10); displayAndPlay(nightmareMusic, 0) ;

В данном cnjae никаких сюрпризов нет, но посмотрим, что произойдет, если заменить обычные указатели на их якобы интеллектуальные аналоги:

void displayAndPlay(const SmartPtr<MusicProduct>& pmp,

int numTimes);

virtual void displayTitle () const = 0;

class Cassette: public MusicProduct { public:

Cassette(const stringb title); virtual void play() const; virtual void displayTitle () const;

class CD: public MusicProduct { public:

CD (const stringSt title) ;

virtual void play 0 const;

virtual void displayTitle () const;



1 ... 53 54 55 [ 56 ] 57 58 59 ... 96

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика