Exceptionsafety make_unique: почему исключение f(new T) безопасно

Я читаю GOTW102 и задаюсь вопросом, почему make_unique более безопасен в плане исключений, чем другие случаи, или в подробно, почему f(new T(...)) более безопасен в плане исключений, чем f(new T1(...), new T2(...)).

Реализация make_unique из блога гласит:

template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args )
{
    return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}

Теперь мне интересно, является ли f(new T(...)) в целом безопасным от исключений (без утечек) или он просто безопасен от исключений в случае make_unique из-за дополнительных знаний, которые конструктор std::unique_ptr не выдает? (Поскольку, если бы это было так, свежесконструированный T все равно бы просочился, насколько я понимаю.


person ted    schedule 29.05.2015    source источник
comment
Связано: stackoverflow.com/q/19472550   -  person dyp    schedule 29.05.2015


Ответы (1)


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

do_work(unique_ptr<A>(new A), unique_ptr<B>(new B));

Компилятору разрешено генерировать код, который выглядит так:

  1. new A
  2. new B // может кинуть!
  3. Построить unique_ptr<A>
  4. Построить unique_ptr<B>
  5. Звоните do_work

Если new B срабатывает, значит, вы просочились в A, потому что unique_ptr никогда не создавалось.

Помещение конструкции unique_ptr в отдельную функцию устраняет эту проблему, поскольку компиляторам не разрешено выполнять тела функций одновременно (поэтому шаги "new" и "construct unique_ptr" необходимо выполнять вместе).


То есть учитывая:

do_work(make_unique<A>(), make_unique<B>())

Компилятор должен сгенерировать код, который выглядит так:

  1. Звоните make_unique<A>
  2. Звоните make_unique<B>
  3. Звоните do_work

or

  1. Звоните make_unique<B>
  2. Звоните make_unique<A>
  3. Звоните do_work

что делает утечку, когда есть новые объекты, плавающие без владения unique_ptrs, невозможной.

person Billy ONeal    schedule 29.05.2015
comment
Я понимаю большую часть этого, мой вопрос больше сосредоточен на make_unique. Он определяется как функция, которая выполняет unique_ptr<A>(new A). Где я могу получить гарантию от того, что unique_ptr<A> не выдает после завершения new A (я не вижу фрагмента кода, который мог бы выдать => он не выдаст). Но если бы конструктор unique_ptr мог генерировать ошибки, он не был бы безопасным для исключений (то есть произошла бы утечка A), верно? - person ted; 29.05.2015
comment
@ted: вы получаете эту гарантию, потому что С++ 11 20.7.1.2 [unique.ptr.single]/1: // 20.7.1.2.1, constructors [...] explicit unique_ptr(pointer p) noexcept; ‹-- объявлено noexcept - person Billy ONeal; 29.05.2015
comment
Между прочим, конструктор shared_ptr из необработанного указателя может выбрасывать (он должен выделить блок управления), но в этом случае он гарантированно удалит указатель, так что ничего не утечет. - person T.C.; 29.05.2015
comment
@T.C.: где я могу найти ссылку на это? - person ted; 29.05.2015
comment
@BillyONeal: я смог найти только N3337, в котором нет 20.7.1.2.1. Итак, что случилось с тем, что опубликовал T.C.: возможен ли случай, когда то, что он сказал, произошло? (Размышляя над этим, поскольку это был единственный случай, который я мог придумать для отказа unique_ptr, я ненадолго подумал, что этого случая не существует, что было неправильно). - person ted; 29.05.2015
comment
@ted Я имел в виду shared_ptr, а не unique_ptr. - person T.C.; 29.05.2015
comment
@ted: Ищите [unique.ptr.single] — номера разделов меняются в разных версиях стандарта. Тем не менее, я не уверен, что unique_ptr находится в 3337; вам может понадобиться копия стандарта не ниже C++11. - person Billy ONeal; 01.06.2015
comment
@ted: попробуйте N3376, первый рабочий документ после C++11. - person Billy ONeal; 01.06.2015