Выбрать главу

В листинге 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);