2016-04-14 2 views
3

Я читаю книгу «C++ Concurrency in action» и пытаюсь понять безопасность исключений в потокобезопасных структурах данных (например, стек). Автор утверждает, что, чтобы избежать состояния гонки, pop должны делать обе операции - выскочить и вернуть элемент из стека, однако:Возврат shared_ptr и безопасности исключений

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

Здесь предлагается решение:

std::shared_ptr<T> pop() 
{ 
    std::lock_guard<std::mutex> lock(m); 
    if(data.empty()) throw runtime_error("empty"); 
    std::shared_ptr<T> const res(std::make_shared<T>(data.top())); 
    data.pop(); 
    return res; 
} 

В этом случае мы получили вызов копии конструктора на make_shared линии. Если конструктор копирования генерирует исключение, стек еще не изменен, и мы хороши.

Однако я не вижу, как она сильно отличается от этого фрагмента кода:

T pop2() { 
    std::lock_guard<std::mutex> lock(m); 
    if(data.empty()) throw runtime_error("empty"); 
    auto res = data.top(); 
    data.pop(); 
    return res; 
} 

Здесь мы имеем копии конструктора вызова на data.top() линии и перемещения конструктора по возвращению. Опять же, если конструктор копирования генерирует исключение, мы хороши, так как стек еще не изменен. Перемещение конструктора не должно допускать исключения.

Я что-то упустил? Какая польза от возврата shared_ptr сравнение с возвратом (подвижным) T?

+1

Перемещение конструкторов разрешено. – Simple

+0

В моей PDF-копии книги 'throw empty_stack()' вместо 'throw runtime_error (" empty ")'. Класс 'empty_stack' в книге наследуется непосредственно из' std :: exception'. Если 'runtime_error' в вашем вопросе является' std :: runtime_error', тогда обратите внимание, что 'throw runtime_error (« empty »);' [может также использовать другие исключения] (https://stackoverflow.com/questions/36106747/как к конструкции-а-stdexcept-исключения-без метания). – jotik

+0

Да, у меня тоже есть 'empty_stack'. Я просто заменил его «runtime_exception» в примере. – lstipakov

ответ

1

Если T не подлежит перемещению, ваш код может закончиться выполнением нескольких копий-конструкций. Использование shared_ptr для размещения копии в куче может быть более эффективным в этом случае.

И даже если T является подвижным, ваш код может по-прежнему выполнять несколько (потенциально дорогостоящих) конструкций перемещения. Известно, что конструкция перемещения и/или копирование shared_ptr относительно дешева по сравнению с перемещением или копированием любого возможного типа T.

Разумеется, ваш пробег может варьироваться в зависимости от конкретных типов, вашего компилятора и операционной среды и оборудования.

+0

Итак, предполагая, что 'T' является подвижным, а move' constructor' не генерирует исключений, * только * преимущество возврата 'shared_ptr' будет сохранять вызов' move constructor' и ничего не связано с безопасностью исключений? – lstipakov

+1

Да, я так думаю. Точнее, он сохраняет одно перемещение-конструкцию 'T', но добавляет движение' shared_ptr '. Второе отличие состоит в том, что 'std :: make_shared' выделяет значение в ** heap **, а в' pop2() 'вы создаете и возвращаете значение в ** стек **, что также может иметь некоторые последствия для производительности относящихся к местоположению памяти. – jotik

1

move-constructor может бросить (в общем). std::shared_ptr<T> имеет конструктор перемещения noexcept.

Так что в первом случае return res; не может бросить, но во втором случае return res; может бросить (и data.pop() уже вызван).

Смежные вопросы