Безопасность исключений и make_unique

Просто чтобы уточнить, использование make_unique повышает безопасность исключений только тогда, когда у вас есть несколько распределений в выражении, а не только одно, верно? Например

void f(T*);

f(new T);

совершенно безопасен для исключений (в отношении выделений и прочего), в то время как

void f(T*, T*);

f(new T, new T);

не является правильным?


person Kal    schedule 20.10.2013    source источник
comment
Ваш вопрос, кажется, противоречит сам себе. Сначала вы утверждаете, что многократное размещение безопасно для исключений, затем вы показываете пример, где это якобы противоположно тому, что происходит.   -  person Lightness Races in Orbit    schedule 20.10.2013
comment
@LightnessRacesinOrbit нет, я утверждал, что множественные распределения небезопасны для исключений. Я сказал, что make_unique добавляет безопасность исключений только тогда, когда у вас есть несколько распределений в выражении, что означает, что оно ничего не добавляет только для одного распределения.   -  person Kal    schedule 20.10.2013
comment
в вашем вопросе не используются make_unique или unique_ptr в ваших примерах, потому что?   -  person Yakk - Adam Nevraumont    schedule 20.10.2013
comment
@Yakk, потому что это не нужно   -  person Kal    schedule 20.10.2013
comment
multiple allocations in an expression, но каждое из этих выделений через new T является отдельным выражением, так что получается наоборот: одно выделение на каждое из нескольких выражений. То, что вызов функции сам по себе является выражением, содержащим два других, не меняет этого. Но если не считать формулировки, вы были на правильном пути. Насколько я читал, правило, сформулированное Саттером, состоит в том, чтобы выполнять каждое выделение в своем собственном операторе или последовательно возвращать их из отдельного вызова функции, тем самым избегая странного упорядочения. и утечки: gotw.ca/gotw/056.htm (старое-но-доброе )   -  person underscore_d    schedule 13.08.2016


Ответы (3)


Не только когда у вас есть несколько распределений, но и всякий раз, когда вы можете бросать в разные места. Учти это:

f(make_unique<T>(), function_that_can_throw());

Против:

f(unique_ptr<T>(new T), function_that_can_throw());

Во втором случае компилятору разрешено вызывать (по порядку):

  • new T
  • function_that_can_throw()
  • unique_ptr<T>(...)

Очевидно, что если function_that_can_throw действительно выбрасывает, то вы теряете. make_unique предотвращает этот случай.

И, конечно же, второе распределение (как в вашем вопросе) - это всего лишь частный случай function_that_can_throw().

Как правило, просто используйте make_unique, чтобы ваш код был последовательным. Это всегда правильно (читай: безопасно для исключений), когда вам нужен unique_ptr, и это не влияет на производительность, поэтому нет причин не использовать его (хотя на самом деле не его использовать вводит много ошибок).

person syam    schedule 20.10.2013
comment
Спасибо, это то, что я искал. Извините, что испортил вашу репутацию 2 ^ 13. - person Kal; 20.10.2013
comment
С небольшим опозданием, но make_unique сама может генерировать в соответствии с cppreference: make_unique может генерировать std::bad_alloc или любое исключение, сгенерированное конструктором T. Если сгенерировано исключение, эта функция не действует. Так как же это исключение безопасно? И что происходит с объектом unique_ptr (именованным или временным), который инициализируется с помощью make_unique, но make_unique выдает броски? - person AdmiralAdama; 23.09.2020
comment
Безопасность исключений — это не (только) отсутствие исключений, а скорее гарантия правильного поведения при получении исключения. Документация make_unique ясна: никакого эффекта, если есть исключение, это надежная гарантия (см. en.wikipedia .org/wiki/Exception_safety). И unique_ptr еще не построен, так что опять же никакого эффекта; исключение просто распространяется. - person syam; 24.09.2020

Начиная с C++17, проблема безопасности исключений исправлена ​​путем переформулировки [expr. звонок]

Инициализация параметра, включая каждое вычисление связанного значения и побочный эффект, неопределенно упорядочена по отношению к любому другому параметру.

Здесь неопределенно упорядоченный означает, что один упорядочен перед другим, но не указано, какой именно.

f(unique_ptr<T>(new T), function_that_can_throw());

Может иметь только два возможных порядка выполнения

  1. new T unique_ptr<T>::unique_ptr function_that_can_throw
  2. function_that_can_throw new T unique_ptr<T>::unique_ptr

Это означает, что теперь это безопасно для исключений.

person Passer By    schedule 05.07.2017

Я думаю, вам лучше сравнивать вещи, используя std::unique_ptr<T>:

void f(std::unique_ptr<T>);

f(std::unique_ptr<T>(new T));
f(std::make_unique<T>());

Ни один из этих вызовов не может протекать, если возникает исключение. Однако

void f(std::unique_ptr<T>, std::unique_ptr<T>);

g(std::unique_ptr<T>(new T), std::unique_ptr<T>(new T));
g(std::make_unique<T>(), std::make_unique<T>());

В этом случае версия, явно использующая std::unique_ptr<T>, может привести к утечке, если возникнет исключение (поскольку компилятор может начать вычисление new-выражений перед созданием любого из временных выражений).

person Dietmar Kühl    schedule 20.10.2013