В листинге 3.4 приведено определение класса стека со свободным от гонок интерфейсом. В нем реализованы приведенные выше варианты 1 и 3: имеется два перегруженных варианта функции-члена pop()
— один принимает ссылку на переменную, в которой следует сохранить значение, а второй возвращает std::shared_ptr<>
. Интерфейс предельно прост, он содержит только функции: push()
и pop()
.
Листинг 3.4. Определение класса потокобезопасного стека
#include <exception>
struct empty_stack: std::exception {
const char* what() const throw();
};
template<typename T>
class threadsafe_stack {
public:
threadsafe_stack();
threadsafe_stack(const threadsafe_stack&);
threadsafe_stack& operator=(const threadsafe_stack&)
= delete;←
(1)
void push(T new_value);
std::shared_ptr<T> pop();
void pop(T& value);
bool empty() const;
};
Упростив интерфейс, мы добились максимальной безопасности — даже операции со стеком в целом ограничены: стек нельзя присваивать, так как оператор присваивания удален (1) (см. приложение А, раздел А.2) и функция swap()
отсутствует. Однако стек можно копировать в предположении, что можно копировать его элементы. Обе функции pop()
возбуждают исключение empty_stack
, если стек пуст, поэтому программа будет работать, даже если стек был модифицирован после вызова empty()
. В описании варианта 3 выше отмечалось, что использование std::shared_ptr
позволяет стеку взять на себя распределение памяти и избежать лишних обращений к new
и delete
. Теперь из пяти операций со стеком осталось только три: push()
, pop()
и empty()
. И даже empty()
лишняя. Чем проще интерфейс, тем удобнее контролировать доступ к данным — можно захватывать мьютекс на все время выполнения операции. В листинге 3.5 приведена простая реализация в виде обертки вокруг класс std::stack<>
.
Листинг 3.5. Определение класса потокобезопасного стека
#include <exception>
#include <memory>
#include <mutex>
#include <stack>
struct empty_stack: std::exception {
const char* what() const throw();
};
template<typename T>
class threadsafe_stack {
private:
std::stack<T> data;
mutable std::mutex m;
public:
threadsafe_stack(){}
threadsafe_stack(const threadsafe_stack& other) {
std::lock_guard<std::mutex> lock(other.m);
data = other.data; ←┐
(1) Копирование производится в теле
} │
конструктора
threadsafe_stack& operator=(const threadsafe_stack&) = delete;
void push(T new_value) {
std::lock_guard<std::mutex> lock(m);
data.push(new_value);
}
std::shared_ptr<T> pop()│
Перед тем как выталкивать значение,
{ ←┘
проверяем, не пуст ли стек
std::lock_guard<std::mutex> lock(m);
if (data.empty()) throw empty_stack();
std::shared_ptr<T> const res(std::make_shared<T>(data.top()));
data.pop(); ←┐
Перед тем как модифицировать стек
return res; │
в функции pop(), выделяем память
} │
для возвращаемого значения
void pop(T& value) {
std::lock_guard<std::mutex> lock(m);
if (data.empty()) throw empty_stack();
value = data.top();
data.pop();
}
bool empty() const {
std::lock_guard<std::mutex> lock(m);