|
Программирование >> Синтаксис инициирования исключений
класс Foo не содержит чисто виртуальных функций. Не важно, объявили ли вы свой собственный конструктор копий; если нет, компилятор построит его за вас. Не важно, есть ли в Foo другие пользовательские конструкторы; в отличие от конструкторов без аргументов, конструктор копий доступен всегда. Строка Foo f2 = f выглядит как присваивание из-за присутствия оператора =, но на самом деле это альтернативный вариант вызова конструктора копий. Чтобы понять, чем присваивание отличается от инициализации, спросите себя: Был ли объект сконструирован заранее или же его создание является частью команды? Если объект уже существует, вы имеете дело с присваиванием. Если он создается на месте, как в пашем примере, используется конструктор копий. При вызове функции Fn() происходит передача по значению копии Foo. Конструктор копий используется для создания временной копии, существующей лишь во время выполнения Fn(). После этого вызывается деструктор копии, который уничтожает ее. Вызов функции Gn(), вероятно, ошибочен, и хороший компилятор прочитает вам суровую нотацию о стиле программирования на C++ - что-нибудь вроде: Создается временная неконстантная копия - поучись программировать, тупица! По крайней мере, со мной компиляторы обычно поступают именно так. Проблема заключается в том, что аргумент передается по ссылке, однако фактический аргумент является константным, а формальный - нет. Все изменения аргумента внутри Gn() вносятся в копию, а не в оригинал. В создаваемом компилятором конструкторе копий по умолчанию используется строго определенная последовательность вызова конструкторов копий базовых классов и переменных класса. 1 . Конструкторы копий базовых классов вызываются в том порядке, в котором они объявлены в списке наследования. 2. Конструкторы копий переменных вызываются в том порядке, в котором они объявлены в объявлении класса. Описанный порядок применяется рекурсивно, то есть первым копируется первый базовый класс первого базового класса... и т. д. Звучит знакомо, не правда ли? Тот же порядок, что и для любого другого конструктора. С конструкторами копий, в отличие от всех остальных, компилятор ведет себя гордо и ревниво. Если вы перегрузите конструктор копий для некоторого класса, компилятор, фигурально выражаясь, умывает руки и отправляется домой. При отсутствии явного вызова конструкторов копий базовых классов и переменных класса в списке инициализации членов вашего собственного конструктора копий компилятор будет использовать конструктор без аргументов для инициализации базовых классов и переменных. class Foo {...}; class Bar : public Foo { private: Foo f; public: Bar(const Bar&); Вероятно, ошибка Bar::Bar(const Bar& b) Стоп! Нет списка инициализации членов Будут использованы конструкторы без аргументов базового класса и переменной Вероятно, ошибки нет Bar::Bar(const Bar& b) : Foo(b), f(b.f) {...} Компилятор очень сильно обидится на первый конструктор копий - так сильно, что он спустит ваше произведение в мусоропровод и даже не сообщит об этом. Для инициализации базового класса и переменной будет использован конструктор Foo без аргументов. В 99 случаях из 100 это совсем не то, чего вы добивались; обычно требуется, чтобы базовые классы и переменные тоже копировались. Вероятно, второй вариант правилен. Базовый класс и переменная присутствуют в списке инициализации членов, поэтому будут вызваны их конструкторы копий (компилятор преобразует b к типу Foo в выражении Foo(b)). В некоторых ситуациях вас интересует именно поведение компилятора по умолчанию. В качестве примера рассмотрим следующий базовый класс, который присваивает уникальный серийный номер каждому производному объекту. class Serialized { private: static int NextSerialNumber; int serialNumber; public: Serialized(const Seria1ized&); Serial ized(); int SerialNumber(); В Serialized.cpp int Seria1ized::NextSerialNumber = 0; Serialized::Seria1ized() : serialNumber(NextSerialNumber++) Seria1ized::Seria1ized(const Seria1ized&) : serialNumber(NextSerialNumber++) int Seria1ized::SerialNumber() return serialNumber; Нac не интересует, какой конструктор - без аргументов или копий - выберет компилятор во время компиляции производного класса, поскольку мы перегрузили оба конструктора, и они делают одно и то же. Закрытые и защищенные конструкторы Конструкторы часто объявляются закрытыми и защищенными, чтобы пользователи не могли создавать экземпляры класса. Если конструктор объявлен закрытым; только обычные и статические функции класса могут создавать стековые экземпляры класса или использовать его в операторе new (по крайней мере, с данным конструктором). Если конструктор объявлен защищенным, пользователь может создавать экземпляры базового класса, поскольку конструктор базового класса может вызываться из конструктора производного класса. Пусть для этого потребуется некоторое воображение, но семантика именно такова. У этой логики есть один недостаток - она не совсем надежна. Если конструктор защищен, любая функция базового или производного класса (включая статические) может создать экземпляр базового класса. class Foo { protected: Foo(); 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(doub1e(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() или отложить ее до первого использования объекта.
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |