Идиома владения позволяет забыть о необходимости освобождать ресурсы. Классическим примером реализации этой идиомы являются интеллектуальные указатели. В этой заметке я бы не хотел касаться темы интеллектуальных указателей, а рассмотреть лишь владение объекта синхронизации на примере критической секции.
Предположим у нас есть класс, объекты которого работают в разных thread одновременно. Допустим у него имеется статический (таким образом общий для потоков) вектор clients хранящий id пользователей. Класс реализовывает интерфейс void AddClient(const int id); Clients::iterator FindClient(const int id); void DelClient(const int id); Совершенно очевидно что эти функции должны работать атомарно, т.е. в момент когда один из потоков добавляет или удаляет клиента другие потоки желающие выполнить операции с вектором должны ждать её завершения. Для этого воспользуемся объектом синхронизации CRITICAL_SECTION. Итак, первая версия нашего класса:
class ClientManager
{
public:
ClientManager(CRITICAL_SECTION &cs_) : cs(cs_)
{
}
~ClientManager()
{
}
void AddClient(const int id)
{
if (FindClient(id) != clients.end())
{
EnterCriticalSection(&cs);
clients.push_back(id);
LeaveCriticalSection(&cs);
}
}
Clients::iterator FindClient(const int id)
{
EnterCriticalSection(&cs);
for (Clients::const_iterator it = clients.begin(); it != cleints.end(); ++it)
if (*it == id)
{
LeaveCriticalSection(&cs);
return it;
}
LeaveCriticalSection(&cs);
return clients.end();
}
void DelClient(const int id)
{
Clients::iterator f_it = FindClient(id);
if (f_it == clients.end())
return;
EnterCriticalSection(&cs);
clients.erase(f_it);
LeaveCriticalSection(&cs);
}
private:
CRITICAL_SECTION &cs;
typedef std::vector<int> Clients;
static Clients clients;
};
Функция FindClient иллюстрирует проблему наиболее ярко. Мы должны освободить критическую секцию в каждой точке выхода из функции. Более того, если после входа в критическую секцию в коде функции до выхода из критической секции произойдет исключение, критическая секция не будет освобождена, что парализует работу всех остальных потоков.
Создадим хелпер входящий в критическую секцию при своем создании и освобождающий её при уничтожении. Поместив его объект на стеке функции мы автоматичски решаем все вышеназванные проблемы.
class CriticalHelper
{
public:
CriticalHelper(CRITICAL_SECTION &cs_) : cs(cs_)
{
EnterCriticalSection(&cs);
}
~CriticalHelper()
{
LeaveCriticalSection(&cs);
}
private:
CRITICAL_SECTION &cs;
static void *operator new(size_t); // Запрещаем создавать объекты в куче
static void operator delete(void *);
};
Обратите внимание, критическая секция не является членом хелпера (членом является ссылка на нее, т. е. объект критическая секция аллоцирован в вызывающем хелпер коде, а хелпер берет его во владение). Это говорит о том, что идиома владения и инкапсуляция не одно и тоже. Хотя разумеется идиому владения можно (и нужно) применять и к инкапсулированным объектам.
Перепишем ClientManager с использованием нашего хелпера:
class ClientManager
{
public:
ClientManager(CRITICAL_SECTION &cs_) : cs(cs_)
{
}
~ClientManager()
{
}
void AddClient(const int id)
{
if (FindClient(id) != clients.end())
{
CriticalHelper(cs);
clients.push_back(id);
}
}
Clients::iterator FindClient(const int id)
{
CriticalHelper(cs);
for (Clients::iterator it = clients.begin(); it != cleints.end(); ++it)
if (*it == id)
return it;
return clients.end();
}
void DelClient(const int id)
{
Clients::iterator f_it = FindClient(id);
if (f_it == clients.end())
return;
CriticalHelper(cs);
clients.erase(f_it);
}
private:
CRITICAL_SECTION &cs;
typedef std::vector<int> Clients;
static Clients clients;
};
Думаю, комментарии излишни. Введение простого хелпера позволило забыть об освобождении критической секция, а также сделать код устойчивым к исключениям. Данная заметка на простом примере иллюстрирует преимущества использования идиомы владения. Эта идиома имеет одно из ключевых значений в современном проектировании C++ приложений.