37
Компилятор очень сильно обидится на первый конструктор копий — так сильно, что он спустит ваше
произведение в мусоропровод и даже не сообщит об этом. Для инициализации базового класса и
переменной будет использован конструктор
Foo
без аргументов. В 99 случаях из 100 это совсем не то,
чего вы добивались; обычно требуется, чтобы базовые классы и переменные тоже копировались.
Вероятно, второй вариант правилен. Базовый класс и переменная присутствуют в списке
инициализации членов, поэтому будут вызваны их конструкторы копий (компилятор преобразует
b
к
типу
Foo
в выражении
Foo(b)
).
В некоторых ситуациях вас интересует именно поведение компилятора по умолчанию. В качестве
примера рассмотрим следующий базовый класс, который присваивает уникальный серийный номер
каждому производному объекту.
class Serialized {
private:
static
int
NextSerialNumber;
int
serialNumber;
public:
Serialized(const
Serialized&);
Serialized();
int
SerialNumber();
};
// В Serialized.cpp
int Serialized::NextSerialNumber = 0;
Serialized::Serialized() : serialNumber(NextSerialNumber++)
{
}
Serialized::Serialized(const Serialized&) : serialNumber(NextSerialNumber++)
{
}
int Serialized::SerialNumber()
{
return
serialNumber;
}
Нac не интересует, какой конструктор — без аргументов или копий — выберет компилятор во время
компиляции производного класса, поскольку мы перегрузили оба конструктора, и они делают одно и
то же.
Закрытые и защищенные конструкторы
Конструкторы часто объявляются закрытыми и защищенными, чтобы пользователи не могли создавать
экземпляры класса. Если конструктор объявлен закрытым; только обычные и статические функции
класса могут создавать стековые экземпляры класса или использовать его в операторе new (по крайней
мере, с данным конструктором). Если конструктор объявлен защищенным, пользователь может
создавать экземпляры базового класса, поскольку конструктор базового класса может «вызываться» из
конструктора производного класса. Пусть для этого потребуется некоторое воображение, но семантика
именно такова. У этой логики есть один недостаток - она не совсем надежна. Если конструктор
защищен, любая функция базового или производного класса (включая статические) может создать
экземпляр базового класса.
class Foo {
protected:
Foo();
};
38
class Bar : public Foo {
public:
Foo*
Fn();
};
Foo Bar::Fn()
{
return
new
Foo;
//
Работает вопреки всем вашим усилиям
}
Возможно, вы полагали, что
Foo
— абстрактный базовый класс и его экземпляры создать невозможно.
Оказывается, ничего подобного! В системе защиты открывается зияющая дыра. Друзья классов
Foo
и
Bar тоже могут создавать экземпляры
Foo
. Единственный «железный» способ, который стопроцентно
гарантирует невозможность создания экземпляров класса — включение в него хотя бы одной чисто
виртуальной функции.
Анонимные экземпляры
Анонимным экземпляром (anonymous instance) называется объект, который... впрочем, сейчас увидите.
struct Point {
int
X;
int
Y;
Point(int x, int y) : X(x), Y(y) {}
};
double distance(Point p)
{
return sqrt(double(p.X) * double(p.X) + double(p.Y) * double(p.Y));
}
double d = distance(Point(17, 29));
Аргумент функции
distance()
представляет собой анонимный экземпляр. Мы не создали переменной
для его хранения. Анонимный экземпляр существует лишь во время вычисления выражения, в котором
он встречается.
Анонимные экземпляры обычно связываются с простыми структурами вроде
Point
, но их можно
использовать для любого класса.
Инициализация глобальных объектов
В спецификации языка порядок конструирования глобальных объектов выглядит довольно сложно.
Если же учесть причуды коммерческих компиляторов C++, этот порядок становится и вовсе
непредсказуемым. В соответствии со спецификацией должны вызываться конструкторы глобальных
объектов, включая конструкторы статических переменных классов и структур, однако многие
компиляторы этого не делают. Если вам повезло и ваш компилятор считает, что конструкторы важны
для глобальных переменных, порядок конструирования глобальных объектов зависит от воображения
разработчика компилятора. Ниже перечислены некоторые правила, которые теоретически должны
соблюдаться:
1. Перед выполнением каких-либо операций все глобальные переменные инициализируются
значением 0.
2. Объекты, находящиеся в глобальных структурах или массивах, конструируются в порядке их
появления в структуре или массиве.
3. Каждый глобальный объект конструируется до его первого использования в программе.
Компилятор сам решает, следует ли выполнить инициализацию до вызова функции
main()
или
отложить ее до первого использования объекта.
Do'stlaringiz bilan baham: |