Программирование >>  Полиморфизм без виртуальных функций в с++ 

1 ... 28 29 30 [ 31 ] 32 33 34 ... 144


class X X; обычный прием в С:

у класса и объекта одно и то же имя

void f{) {

...

Х.а; что означает X: класс или объект? . . .

Чтобы справиться с данной проблемой, для обозначения членства в классе была введена нотация : :, а . (точка) оставлена только для обозначения членов объекта. Тогда приведенный выше пример записывается так:

void X::set(X arg) { а = arg.а; };

class X X;

void g() {

...

Х.а; объект.член Х::а; класс::член . . .

3.11.4. Инициализация глобальных объектов

Я поставил целью разрешить использование определенных пользователем типов всюду, где допустимы встроенные типы. По опыту работы с Simula было известно, что отсутствие глобальных переменных типа класса - одна из причин снижения производительности. В С++ они были разрешены. Это имело важные и в че,м-то неожиданные последствия. Рассмотрим пример:

class Double { ...

Double(double);

Double sl = 2; конструирование sl из 2

Double s2 = sqrt(2); конструирование s2 из sqrt(2)

В общем случае такую инициализацию нельзя выполнить ни во время компиляции, ни во время связывания. Необходима динамическая инициализация (во вре.мя исполнения). Она выполняется в том порядке, в котором объявления встречаются в единице трансляции. Порядок инициализации объектов из разных единиц трансляции не определен. Гарантируется лишь, что статическая инициализация будет выполнена раньше динамической.



3.11.4.1. Проблемы динамической инициализации

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

В библиотеке часто бывает необходимо выполнить какие-то действия до того, как будут использоваться отдельные функции из нее. С другой стороны, в библиотеке могут быть объекты, которые следовало бы инициализировать предварительно, чтобы пользователь мог сразу применять их, не задумываясь об инициализации. Например, вы же не инициализируете пере.менные stdin и stdout из стандартной библиотеки С, за вас это делает стартовый модуль. А закрывает эти потоки библиотечная функция exit (). Это очень специфическая ситуация, ничего подобного в других библиотеках нет. Когда я проектировал библиотеку потокового ввода/вывода, то хотел добиться такого же удобства, не вводя в С-ы-специализированных расширений. Поэтому было решено воспользоваться динамической инициализацией.

И все бы хорошо, вот только приходилось полагаться на деталь реализации, заключавшуюся в то.м, что cin и cout конструируются до начала выполнения пользовательского кода и уничтожаются после его завершения. Другие разработчики оказались не такими внимательными. В результате программы аварийно завершались, поскольку cout использовался еще до того, как был сконструирован. С другой стороны, часть выходных данных терялась из-за того, что cout уничтожался (со сбросом буферов) слишко.м рано. Другими слова.ми, мы попались на той самой зависи.мости от порядка выполнения, которую я считал маловероятной и признаком плохого дизайна .

3.11.4.2. Как обойти зависимость от порядка

Есть два реп1ения данной проблемы. Очевидное - добавить к каждой функции-члену флажок, сигнализирующий, в первый ли раз выполняется фрагмент данного кода. Для этого необходима некая глобальная переменная, инициализированная нулем. Напри.мер:

class Z {

static int first time;

void init{);

. . . public:

void fl{) ;

. . .

void fn() ;



Каждая функция-член при этом выглядела бы примерно так:

void Z::fl() {

if (first time == 0) { initO ;

first time = 1;

. . .

Накладные расходы для таких простых функций, как вывод одного символа, могут оказаться слишком велики.

В новом дизайне потокового ввода/вывода (см. раздел 8.3.1) Джерри Шварц (Jerry Schwarz) применил хитроумный вариант этого приема [Schwarz, 1989]. Заголовок <iostream.h> содержит примерно такой код:

class io counter {

static int count; public:

io counter0

if {count++ == 0) { /* инициализировать cin, cout и т.д. */ }

~io counter() {

if {--count == 0) { /* очистить cin, cout и т.д. */ }

static io counter io init;

Теперь каждый файл, включающий заголовок <iostream. h>, создает и инициализирует также объект io counter, в результате чего увеличивается счетчик io counter: : count. Когда это происходит в первый раз, инициализируются библиотечные объекты. Поскольку заголовок предшествует любому использованию библиотечных средств, правильная инициализация гарантируется. Так как деструкторы вызываются в порядке, обратно.м вызову конструкторов, этот прием гарантирует также корректную очистку вслед за последним использованием библиотеки.

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



1 ... 28 29 30 [ 31 ] 32 33 34 ... 144

© 2006 - 2025 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика