Чтение онлайн

ЖАНРЫ

C++. Сборник рецептов

Когсуэлл Джефф

Шрифт:

Ссылки более сложны: инициализация переменной-ссылки (и

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 (абстрактная фабрика).

Решение

Здесь есть две возможности. Вы можете:

• создать функцию, которая создает экземпляр объекта в куче и возвращает указатель на этот объект (или обновляет переданный в нее указатель, записывая в него адрес нового объекта);

• создать функцию, которая создает и возвращает временный объект.

Пример 8.2 показывает оба этих способа. Класс

Session
в этом примере может быть любым классом, объекты которого должны не создаваться непосредственно в коде (т.е. с помощью
new
), а их создание должно управляться каким-либо другим классом. В этом примере управляющий класс — это
SessionFactory
.

Пример 8.2. Функции, создающие объекты

#include <iostream>

class Session {};

class SessionFactory {

public:

 Session Create;

 Session* CreatePtr;

 void Create(Session*& p);

 // ...

};

//
Возвращаем копию объекта в стеке

Session SessionFactory::Create {

 Session s;

 return(s);

}

// Возвращаем указатель на объект в куче

Session* SessionFactory::CreatePtr {

 return(new Session);

}

// Обновляем переданный указатель, записывая адрес

// нового объекта

void SessionFactory::Create(Session*& p) {

 p = new Session;

}

static SessionFactory f; // Единственный объект фабрики

int main {

 Session* p1;

 Session* p2 = new Session;

 *p2 = f.Create; // Просто присваиваем объект, полученный из Create

 p1 = f.CreatePtr; // или полученный указатель на объект в куче

 f.Create(p1); // или обновляем указатель новым адресом

}

Обсуждение

Пример 8.2 показывает несколько различных способов написания функции, возвращающей объект. Сделать так вместо обращения к

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

Наиболее часто используют возврат адреса нового объекта в куче или обновление адреса указателя, переданного как аргумент. Их реализация показана в примере 8.2, и она тривиальна и не требует дальнейших пояснений. Однако возврат из функции целого объекта используется реже — возможно, потому, что это требует больших накладных расходов.

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

Поделиться с друзьями: