Рейтинговые книги
Читем онлайн C++. Сборник рецептов - Д. Стефенс

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 70 71 72 73 74 75 76 77 78 ... 136

Следующая запись в 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, и она тривиальна и не требует дальнейших пояснений. Однако возврат из функции целого объекта используется реже — возможно, потому, что это требует больших накладных расходов.

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

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

Если вы пишете функцию или метод, создающий объекты, то посмотрите также рецепт 8.12. Используя шаблоны, функций можно написать одну функцию, которая будет возвращать новый объект любого типа. Например:

template<typename T>

T* createObject() {

 return(new T());

}

MyClass* p1 = createObject();

MyOtherClass* p2 = createObject();

// ...

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

Смотри также

Рецепт 8.12.

8.3. Использование конструкторов и деструкторов для управления ресурсами (RAII)

Проблема

Для класса, представляющего некоторый ресурс, требуется использовать конструктор для получения этого ресурса и деструктор для его освобождения. Эта методика часто называется «получение ресурсов как инициализация» (resource acquisition is initialization — RAII).

Решение

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

Пример 8.3. Использование конструкторов и деструкторов

#include <iostream>

#include <string>

using namespace std;

class Socket {

public:

 Socket(const string& hostname) {}

};

class HttpRequest {

public:

 HttpRequest(const string& hostname) :

  sock_(new Socket(hostname)) {}

 void send(string soapMsg) {sock << soapMsg;}

 ~HttpRequest() {delete sock_;}

private:

 Socket* sock_;

};

void sendMyData(string soapMsg, string host) {

 HttpRequest req(host);

 req.send(soapMsg);

 // Здесь делать ничего не требуется, так как когда req выходит

 // за диапазон, все очищается.

}

int main() {

 string s = "xml";

 sendMyData(s, "www.oreilly.com");

}

Обсуждение

Гарантии, даваемые конструкторами и деструкторами, представляют собой удобный способ заставить компьютер выполнить всю очистку за вас. Обычно инициализация объекта и выделение используемых ресурсов производится в конструкторе, а очистка — в деструкторе. Это нормально. Но программисты имеют склонность использовать последовательность событий «создание-открытие-использование-закрытие», когда пользователю класса требуется выполнять явные открытия и закрытия ресурсов. Класс файла является хорошим примером.

1 ... 70 71 72 73 74 75 76 77 78 ... 136
На этой странице вы можете бесплатно читать книгу C++. Сборник рецептов - Д. Стефенс бесплатно.

Оставить комментарий