Категории
Самые читаемые
Лучшие книги » Компьютеры и Интернет » Программирование » C++. Сборник рецептов - Д. Стефенс

C++. Сборник рецептов - Д. Стефенс

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

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 71 72 73 74 75 76 77 78 79 ... 136
Перейти на страницу:

Примерно так звучит обычный аргумент в пользу RAII. Я легко мог бы создать в примере 8.3 свой класс HttpRequest, который заставил бы пользователя выполнить несколько больше работы. Например:

class HttpRequest {

public:

 HttpRequest();

 void open(const std::string& hostname);

 void send(std::string soapMsg);

 void close();

 ~HttpRequest();

private:

 Socket* sock_;

};

При таком подходе соответствующая версия sendMyData может выглядеть так.

void sendMyData(std::string soapMsg, std::string host) {

 HttpRequest req;

 try {

  req.open();

  req.send(soapMsg);

  req.close();

 } catch (std::exception& e) {

  req.close(); // Do something useful...

 }

}

Здесь требуется выполнить больше работы без каких бы то ни было преимуществ. Этот дизайн заставляет пользователя писать больше кода и работать с исключениями, очищая ваш класс (при условии, что в деструкторе close не вызывается).

Подход RAII имеет широкое применение, особенно когда требуется гарантировать, что при выбрасывании исключения будет выполнен «откат» каких-либо действий, позволяя при этом не загромождать код бесконечными try/catch. Рассмотрим настольное приложение, которое в процессе выполнения какой-либо работы отображает в строке состояния или заголовка сообщение.

void MyWindow : thisTakesALongTime() {

 StatusBarMessage("Copying files...");

 // ...

}

Все, что класс StatusBarMessage должен сделать, — это использовать информацию о статусе для обновления соответствующего окна при создании и вернуть его первоначальное состояние при удалении. Вот ключевой момент: если функция завершает работу или выбрасывается исключение, StatusBarMessage все равно выполнит работу. Компилятор гарантирует, что при выходе из области видимости стековой переменной для нее будет вызван ее деструктор. Без этого подхода автор thisTakesALongTime должен был бы принять во внимание все пути передачи управления, чтобы неверное сообщение не осталось в окне при неудачном завершении операции, ее отмене пользователем и т.п. И снова повторю, что этот подход приводит к уменьшению кода и снижению числа ошибок автора вызывающего кода.

RAII не является панацеей, но если вы его еще не использовали, то вы, скорее всего, найдете немало возможностей для его применения. Еще одним хорошим примером является блокировка. При использовании RAII для управления блокировками ресурсов, таких как потоки, объекты пулов, сетевые соединения и т.п., этот подход позволяет создавать более надежный код меньшего размера. На самом деле именно так многопоточная библиотека Boost реализует блокировки, делая программирование пользовательской части более простым. За обсуждением библиотеки Boost Threads обратитесь к главе 12.

8.4. Автоматическое добавление новых экземпляров класса в контейнер

Проблема

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

Решение

Включите в класс статический член, являющийся контейнером, таким как list, определенный в <list>. Добавьте в этот контейнер адрес объекта при его создании и удалите его при уничтожении. Пример 8.4 показывает, как это делается.

Пример 8.4. Отслеживание объектов

#include <iostream>

#include <list>

#include <algorithm>

using namespace std;

class MyClass {

protected:

 int value_;

public:

 static list<MyClass*> instances_;

 MyClass(int val);

 ~MyClass();

 static void showList();

};

list<MyClass*> MyClass::instances_;

MyClass::MyClass(int val) {

 instances_.push_back(this);

 value_ = val;

}

MyClass::~MyClass() {

 list<MyClass*>::iterator p =

  find(instances_.begin(), instances_.end(), this);

 if (p != instances_.end()) instances_.erase(p);

}

void MyClass::showList() {

 for (list<MyClass*>::iterator p = instances_.begin();

  p != instances_.end(); ++p)

  cout << (*p)->value_ << endl;

}

int main() {

 MyClass a(1);

 MyClass b(10);

 MyClass с(100);

 MyClass::showList();

}

Пример 8.4 создаст следующий вывод.

1

10

100

Обсуждение

Подход в примере 8.4 очень прост: используйте для хранения указателей на объекты static list. При создании объекта его адрес добавляется в list; при его уничтожении он удаляется. Здесь имеется пара важных моментов.

При использовании любых членов-данных типа static их требуется объявлять в заголовочном файле класса и определять в файле реализации. Пример 8.4 весь находится в одном файле, так что здесь это не применимо, но помните, что переменную типа static требуется определять в файле реализации, а не в заголовочном файле. За объяснением причин обратитесь к рецепту 8.5.

Вы не обязаны использовать член static. Конечно, можно использовать глобальный объект, но тогда дизайн не будет таким «замкнутым». Более того, вам где-то еще придется выделять память для глобального объекта, передавать его в конструктор MyClass и в общем случае выполнять еще целый ряд действий.

Помните, что совместное использование глобального контейнера, как в примере 8.4, не будет работать, если объекты класса MyClass создаются в нескольких потоках. В этом случае требуется сериализация доступа к общему объекту через мьютексы. Рецепты, относящиеся к этой и другим методикам многопоточности, приведены в главе 12.

Если требуется отслеживать все экземпляры класса, можно также использовать шаблон фабрики. В целом это будет означать, что для создания нового объекта клиентский код вместо вызова оператора new должен будет вызывать функцию. За подробностями о том, как это делается, обратитесь к рецепту 8.2.

Смотри также

Рецепт 8.2.

8.5. Гарантия единственности копии переменной-члена

Проблема

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

Решение

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

Пример 8.5. Использование статических переменных-членов

// Static.h

class OneStatic {

public:

 int getCount() {return count;}

 OneStatic();

protected:

 static int count;

};

// Static.cpp

#include "Static.h"

int OneStatic::count = 0;

OneStatic::OneStatic() {

 count++;

}

// StaticMain.cpp

#include <iostream>

#include "static.h"

using namespace std;

int main() {

 OneStatic a;

 OneStatic b;

 OneStatic c;

 cout << a.getCount() << endl;

 cout << b.getCount() << endl;

 cout << c.getCount() << endl;

}

Обсуждение

static — это способ C++ разрешить создание только одной копии чего-либо. Если переменную-член объявить как static, то будет создана только одна такая переменная вне зависимости от количества созданных объектов этого класса. Аналогично, если объявить как static переменную функции, она будет создана только один раз и будет хранить свое значение от одного вызова функции к другому. Однако в случае с переменными-членами, чтобы убедиться, что переменная создана правильно, требуется проделать несколько больше работы. Именно по этой причине в примере 8.5 показано три файла.

Во-первых, при объявлении переменной требуется использовать ключевое слово static. Это достаточно просто: добавьте это ключевое слово в заголовок класса, находящийся в заголовочном файле Static.h.

protected:

static int count;

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

int OneStatic::count = 0;

В примере 8.5 я поместил это определение в файл Static.cpp. Именно так вы и должны делать — не помещайте определение в заголовочный файл. Если это сделать, память будет выделена в каждом файле реализации, включающем этот заголовочный файл, и либо возникнут ошибки компиляции, либо, что хуже, в памяти появятся несколько экземпляров этой переменной. Это не то, что требуется при использовании переменной-члена static.

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