Введение в язык Си++

         

Предостережение


Если x и y - объекты класса cl, то x=y в стандартном случае означает побитовое копирование y в x (см. ). Такая интерпретация присваивания может привести к изумляющему (и обычно нежелательному) результату, если оно применяется к объектам класса, для которого определены конструктор и деструктор. Например:

class char_stack { int size; char* top; char* s; public: char_stack(int sz) { top=s=new char[size=sz]; } ~char_stack() { delete s; } // деструктор void push(char c) { *top++ = c; } char pop() { return *--top; } };

void h() { char_stack s1(100); char_stack s2 = s1; // неприятность char_stack s3(99); s3 = s2; // неприятность }

Здесь конструктор char_stack::char_stack() вызывается дважды: для s1 и для s3. Для s2 он не вызывается, поскольку эта переменная инициализируется присваиванием. Однако деструктор char_stack::~char_stack() вызывается трижды: для s1, s2 и s3! Кроме того, по умолчанию действует интерпретация присваивания как побитовое копирование, поэтому в конце h() каждый из s1, s2 и s3 будет содержать указатель на вектор символов, размещенный в свободной памяти при создании s1. Не останется никакого указателя на вектор символов, выделенный при создании s3. Таких отклонений можно избежать: см.


Когда в конструкторе производится присваивание указателю this, значение this до этого присваивания не определено. Таким образом, ссылка на член до этого присваивания не определена и скорее всего приведет к катастрофе. Имеющийся компилятор не пытается убедиться в том, что присваивание указателю this происходит на всех траекториях выполнения:

mytype::mytype(int i) { if (i) this = mytype_alloc(); // присваивание членам };

откомпилируется, и при i==0 никакой объект размещен не будет.

Конструктор может определить, был ли он вызван операцией new, или нет. Если он вызван new, то указатель this на входе имеет нулевое значение, в противном случае this указывает на пространство, уже выделенное для объекта (например, на стек). Поэтому можно просто написать конструктор, который выделяет память, если (и только если) он был вызван через new. Например:

mytype::mytype(int i) { if (this == 0) this = mytype_alloc(); // присваивание членам };

Эквивалентного средства, которое позволяет деструктору решить вопрос, был ли его объект создан с помощью new, не имеется, как нет и средства, позволяющего ему узнать, вызвала ли его delete, или он вызван объектом, выходящим из области видимости. Если для пользователя это существенно, то он может сохранить где-то соответствующую информацию для деструктора. Другой способ, - когда пользователь обеспечивает, что объекты этого класса размещаются только соответствующим образом. Если удается справиться с первой проблемой, то второй способ интереса не представляет.

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




Как и большая часть возможностей в языках программирования, перегрузка операций может применяться как правильно, так и неправильно. В частности, можно так воспользоваться возможность определять новые значения старых операций, что они станут почти совсем непостижимы. Представьте, например, с какими сложностями столкнется человек, читающий программу, в которой операция + была переопределена для обозначения вычитания.

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

Может быть. разумно применять перегрузку операций главным образом так, чтобы подражать общепринятому применению операций. В тех случаях, когда нет общепринятой операции или имеющееся в C++ множество операций не подходит для имитации общепринятого применения, можно использовать запись вызова функции.



Содержание раздела