Рассмотрим простой строковый класс string:
struct string { char* p; int size; // размер вектора, на который указывает p
string(int size) { p = new char[size=sz]; } ~string() { delete p; } };
Строка - это структура данных, содержащая указатель на вектор символов и размер этого вектора. Вектор создается конструктором и удаляется деструктором. Но как мы видели в §5.5.1 здесь могут возникнуть проблемы:
void f() { string s1(10); string s2(20) s1 = s2; }
Здесь будут размещены два символьных вектора, но в результате присваивания s1 = s2 указатель на один из них будет уничтожен, и заменится копией второго. По выходе из f() будет вызван для s1 и s2 деструктор, который дважды удалит один и тот же вектор, результаты чего по всей видимости будут плачевны. Для решения этой проблемы нужно определить соответствующее присваивание объектов типа string:
struct string { char* p; int size; // размер вектора, на который указывает p
string(int size) { p = new char[size=sz]; } ~string() { delete p; } string& operator=(const string&); };
string& string::operator=(const string& a) { if (this !=&a) { // опасно, когда s=s delete p; p = new char[size=a.size]; strcpy(p,a.p); } return *this; }
При таком определении string предыдущий пример пройдет как задумано. Но после небольшого изменения в f() проблема возникает снова, но в ином обличии:
void f() { string s1(10); string s2 = s1; // инициализация, а не присваивание }
Теперь только один объект типа string строится конструктором string::string(int), а уничтожаться будет две строки. Дело в том, что пользовательская операция присваивания не применяется к неинициализированному объекту. Достаточно взглянуть на функцию string::operator(), чтобы понять причину этого: указатель p будет тогда иметь неопределенное, по сути случайное значение.
Как правило, в операции присваивания предполагается, что ее параметры проинициализированы. Для инициализации типа той, что приведена в этом примере это не так по определению. Следовательно, чтобы справиться с инициализацией нужна похожая, но своя функция: