41
class Bar : public Foo {
private:
int*
numbers;
public:
Bar() : numbers(new int[17]) {...}
~Bar();
};
Bar* b = new Bar;
delete b; //
Вызывает Bar::~Bar()
Foo* f = new Bar;
delete f; //
Ой! Вызывается Foo::Foo()!
При удалении
f
массив, на который ссылается переменная numbers, превращается в некое подобие
Летучего Голландца, обреченного на вечные скитания в памяти. Чтобы избежать беды, достаточно
объявить оба деструктора виртуальными; в этом случае независимо от типа указателя (кроме, конечно,
void*
) уничтожение будет начинаться с
Bar: :~Ваг()
.
Другая, более коварная проблема с невиртуальными деструкторами возникает при организации
нестандартного управления памятью. Компилятор сообщает вашему перегруженному оператору
размер уничтожаемого объекта — сюрприз! Для невиртуального деструктора этот размер может
оказаться неверным. Представьте себе удивление вашей программы, когда ей сообщат, что объект
имеет размер 20 байт, хотя на самом деле он равен 220 байтам! Разработчики компиляторов C++ любят
похвастаться подобными проделками за кружкой пива после работы.
Мораль: деструкторы следует делать виртуальными. Исключение составляют ситуации, когда ваш
класс или структура не имеет производных классов или у вас найдутся чрезвычайно веские причины
поступить иначе.
Прямой вызов деструкторов
Деструктор можно вызвать и напрямую, не прибегая к оператору
delete
, поскольку это такая же
функция, как и все остальные. Впрочем, до того, как мы займемся нестандартным управлением
памятью, вряд ли это будет иметь какой-нибудь смысл.
class Foo {
public:
~Foo();
};
Foo* f = new Foo;
f->Foo::~Foo();
Позднее мы воспользуемся этой возможностью, а пока сохраните ее в своей коллекции C++.
Присваивание
Присваивание одного объекта другому в C++ — дело серьезное. Впрочем, в обилии запутанных правил
есть и положительная сторона — благодаря им вы постоянно остаетесь начеку и уделяете больше
внимания программе.
Синтаксис и семантика присваивания
Для присваивания одного объекта другому используется оператор
=
.
Foo f;
Foo f1;
f1 = f;
Присваивание выполняется в третьей строке. Если бы
f
и
f1
были целыми или чем-нибудь столь же
простым, смысл этой строки был бы предельно ясен: содержимое области памяти, на которую
42
ссылается
f
, копируется в область памяти, на которую ссылается
f1
. Только и всего. Но если
Foo
относится к нетривиальному классу, в C++ все заметно усложняется. В приведенном примере
компилятор предоставляет оператор
=
по умолчанию, который вызывается для выполнения
фактического копирования. Как и с конструкторами копий, вы можете спокойно сидеть и смотреть, как
компилятор вкалывает за вас, или написать свой собственный оператор
=
. То, что делает версия по
умолчанию, вам может и не понравиться, особенно в момент освобождения памяти деструктором
класса.
class String {
private:
char*
s;
public:
String(char*);
~String();
void Dump(ostream& os);
};
String::String(char* str) : s(NULL)
{
if (str == NULL) {
// NULL означает пустую строку
s = new char[1];
*s = ‘\0’;
}
else
{
s = new char[strlen(str) + 1];
strcpy(s,
str);
}
}
String::~String()
{
delete
s;
}
void String::Dump(ostream& os)
{
os << “\”” << s << “\””;
}
String* s1 = new String(“Hello”);
String* s2 = new String(“Goodbye”);
s2 = s1;
delete s1;
// Память освободилась, вроде все нормально...
s2->Dump(); //
Облом! Ха-ха-ха!
delete s2;
// Помогите, убивают! Ха-ха-ха!
По умолчанию компилятор копирует содержимое
s2->s
поверх содержимого
s1->s
. При этом
копируется значение указателя, а не символы, поэтому после присваивания возникают две большие
проблемы. Два разных объекта ссылаются на одну область памяти, и никто не ссылается на копию
Goodbye
, созданную командой
String* s2 = new String( "Goodbye");
. Дальше — больше; при
удалении
s1
деструктор освобождает область памяти, на которую ссылается
s1
. Однако на эту память
продолжает ссылаться указатель
s2->s
. Попытка вывести
s2->s
дает совершенно безумные
результаты. «Комедия ошибок» достигает кульминации при попытке удалить
s2
, поскольку менеджер
памяти попытается освободить ранее освобожденную область. Чего только не бывает в C++!
Do'stlaringiz bilan baham: |