Выбрать главу

Объектно-ориентированное проектирование и связанные с ним шаблоны проектирования — это обширный вопрос, и имеется большое количество различной литературы на эту тему. В этой главе я упоминаю названия только некоторых шаблонов проектирования, и это шаблоны, для которых возможности C++ обеспечивают элегантное или, возможно, не совсем очевидное решение. Если вы не знакомы с концепцией шаблонов проектирования, я рекомендую прочесть книгу Design Patterns (Addison Wesley), поскольку это полезная вещь при разработке программного обеспечения. Однако для этой главы знание шаблонов проектирования не требуется.

8.1. Инициализация переменных-членов класса

Проблема

Требуется инициализировать переменные-члены, которые имеют встроенные типы, являются указателями или ссылками.

Решение

Для установки начальных значений переменных членов используйте список инициализации. Пример 8.1 показывает, как это делается для встроенных типов, указателей и ссылок.

Пример 8.1. Инициализация членов класса

#include <string>

using namespace std;

class Foo {

public:

 Foo() : counter_(0), str_(NULL) {}

 Foo(int c, string* p) : counter_(c), str_(p) {}

private:

 int counter_;

 string* str_;

};

int main() {

 string s = "bar";

 Foo(2, &s);

}

Обсуждение

Переменные встроенных типов следует всегда инициализировать, особенно если они являются членами класса. С другой стороны, переменные класса должны иметь конструктор, который корректно инициализирует их состояние, так что самостоятельно инициализировать их не требуется. Сохранить неинициализированное состояние переменных встроенных типов, когда они содержат мусор, — значит напрашиваться на проблемы. Но в C++ есть несколько различных способов выполнить инициализацию, и они описываются в этом рецепте.

Простейшими объектами инициализации являются встроенные типы. Работать с int, char и указателями очень просто. Рассмотрим простой класс и его конструктор по умолчанию.

class Foo {

public:

 Foo() : counter_(0), str_(NULL) {}

 Foo(int c, string* p) : counter_(c), str_(p) {}

private:

 int counter_;

 string* str_;

};

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

Члены инициализируются в порядке их указания в объявлении класса, а не в порядке объявления их в списке инициализации.

Используя тот же класс Foo, как и в примере 8.1, рассмотрим переменную-член класса.

class Foo {

public:

 Foo() : counter_(0), str_(NULL), cls_(0) {}

 Foo(int с, string* p) :

  counter_(c), str_(p), cls_(0) {}

private:

 int counter_;

 string* str_;

 SomeClass cls_;

};

В конструкторе по умолчанию Foo инициализировать cls_ не требуется, так как будет вызван ее конструктор по умолчанию. Но если требуется создать Foo с аргументами, то следует добавить аргумент в список инициализации, как это сделано выше, а не делать присвоение в теле конструктора. Используя список инициализации, вы избежите дополнительного шага создания cls_ (так как при присвоении cls_ значения в теле конструктора cls_ вначале создается с использованием конструктора по умолчанию, а затем с помощью оператора присвоения выполняется присвоение нового значения), а также получите автоматическую обработку исключений. Если объект создается в списке инициализации и этот объект в процессе его создания выбрасывает исключение, то среда выполнения удаляет все ранее созданные объекты списка и передает исключение в код, вызывавший конструктор. С другой стороны, при присвоении аргумента в теле конструктора такое исключение необходимо обрабатывать с помощью блока try/catch.

Ссылки более сложны: инициализация переменной-ссылки (и const-членов) требует обязательного использования списка инициализации. В соответствии со стандартом ссылка всегда должна ссылаться на одну переменную и никогда не может измениться и ссылаться на другую переменную. Переменная-ссылка никогда не может не ссылаться на какой-либо объект. Следовательно, чтобы присвоить что-то осмысленное переменной-члену, являющейся ссылкой, это должно происходить при инициализации, т.е. в списке инициализации.

Следующая запись в C++ недопустима.

int& x;

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

int а;

int& x = a;

Это все замечательно, но приводит к возникновению проблемы при создании классов. Предположим, вам требуется переменная-член класса, являющаяся ссылкой, как здесь.

class HasARef {

public:

 int& ref;

};

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

HasARef me;

В этот момент вы получите ошибку. Вот какую ошибку выдаст gcc.

error: structure 'me' with uninitialized reference members

(ошибка: структура 'me' с неинициализированными членами-ссылками)

Вместо этого следует использовать список инициализации.

class HasARef {

public:

 int &ref;

 HasARef(int &aref) : ref(aref) {}

};

Затем при создании экземпляра класса требуется указать переменную, на которую будет ссылаться переменная ref, как здесь.

int var;

HasARef me(var);

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

Смотри также

Рецепт 9.2.

8.2. Использование функции для создания объектов (шаблон фабрики)

Проблема

Вместо создания объекта в куче с помощью new вам требуется функция (член или самостоятельная), выполняющая создание объекта, тип которого определяется динамически. Такое поведение достигается с помощью шаблона проектирования Abstract Factory (абстрактная фабрика).