Вот как этот прием применяется к объектам tfs и tempDir:
class FileSystem {...}; // как раньше
FileSystem& tfs() // эта функция заменяет объект tfs, она может
{ // быть статической в классе FileSystem
static FileSystem fs; // определение и инициализация локального
// статического объекта
return fs; // возврат ссылки на него
}
class Directory {...}; // как раньше
Directory::Directory( params ) // как раньше, но вместо ссылки на tfs
{ // вызов tfs()
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir() // эта функция заменяет объект tempDir,
{ // может быть статической в классе Directory
static Directory td; // определение/инициализация локального
// статического объекта
return td; // возврат ссылки на него
}
Клиенты работают с этой модифицированной программой так же, как раньше, за исключением того, что вместо tfs и tempDir они теперь обращаются к tfs() и tempDir(). Иными словами, используют ссылки на объекты, возвращенные функциями, вместо использования самих объектов.
Функции, которые в соответствии с данной схемой возвращают ссылки, всегда просты: определить и инициализировать локальный статический объект в строке 1 и вернуть его в строке 2. В связи с этим у вас может возникнуть искушение объявить их встроенными, особенно, если они часто вызываются (см. правило 30). С другой стороны, тот факт, что эти функции содержат в себе статические объекты, усложняет их применение в многопоточных системах. Но тут никуда не деться: неконстантные статические объекты любого рода – локальные или нелокальные – представляют проблему в случае наличия в программе нескольких потоков. Решить ее можно, например, вызвав самостоятельно все функции, возвращающие ссылки, на этапе запуска программы, когда еще работает только один поток. Это исключит неопределенность в ходе инициализации.
Конечно, применимость идеи функций, возвращающих ссылки, для предотвращения проблем, связанных с порядком инициализации, зависит от того, существует ли в принципе разумный порядок инициализации ваших объектов. Если вы напишете код, в котором объект A должен быть инициализирован прежде, чем объект B, и одновременно сделаете инициализацию A зависимой от инициализации B, то вас ждут проблемы – и поделом! Если, однако, вы будете избегать таких патологических ситуаций, то описанная схема сослужит вам добрую службу, по крайней мере, в однопоточных приложениях.
Таким образом, чтобы избежать использования объектов до их инициализации, вам следует сделать три вещи. Первое: вручную инициализировать не являющиеся членами объекты встроенных типов. Второе: использовать списки инициализации членов для всех частей объекта. И наконец, третье: обойти за счет правильного проектирования проблему негарантированного порядка инициализации нелокальных статических объектов, определенных в разных единицах трансляции.
Что следует помнить
• Всегда вручную инициализировать объекты встроенных типов, поскольку C++ делает это, только не всегда.
• В конструкторе отдавать предпочтение применению списков инициализации членов перед прямым присваиванием значений в теле конструктора. Перечисляйте данные-члены в списке инициализации в том же порядке, в каком они объявлены в классе.
• Избегайте проблем с порядком инициализации в разных единицах трансляции, заменяя нелокальные статические объекты локальными статическими объектами.
Глава 2
Конструкторы, деструкторы и операторы присваивания
Почти во всех ваших классах будут определены один или несколько конструкторов, деструктор и оператор присваивания. Это функции, которые отвечают за операции создания и инициализации объекта, его уничтожения, а также присваивания ему нового значения. Ошибки в этих функциях приводят к далеко идущим и неприятным последствиям и отражаются на всех ваших классах, поэтому они должны быть написаны правильно. В настоящей главе изложены правила по программированию функций, составляющих основу классов.
Правило 5: Какие функции C++ создает и вызывает молча
Когда пустой класс перестает быть пустым? Когда за него берется C++. Если вы не объявите конструктор копирования, оператор присваивания или деструктор самостоятельно, то компилятор сделает это за вас. Более того, если вы не объявите вообще никакого конструктора, то компилятор автоматически создаст конструктор по умолчанию. Все эти функции будут открытыми и встроенными (см. правило 30). Например, такое объявление:
Читать дальше
Конец ознакомительного отрывка
Купить книгу