web-dev-qa-db-ja.com

std :: unique_ptr :: reset()が常にnoexceptなのはなぜですか?

最近の質問 (特にそれに対する私の答え)に不思議に思いました:

C++ 11(およびより新しい標準)では、特に指定されない限り、デストラクタは常に暗黙的にnoexceptです(つまり、noexcept(false))。その場合、これらのデストラクタは法的に例外をスローする場合があります。 (これはまだであることに注意してください。あなたは何をしているのかを本当に知っている必要があります-種類の状況!)

ただし、std::unique_ptr<T>::reset()のすべてのオーバーロードは、noexceptがデストラクタでない場合でも、常にTになるように宣言されます( cppreference を参照)。 reset()の間にデストラクタが例外をスローすると、プログラムが終了します。 std::shared_ptr<T>::reset()にも同様のことが当てはまります。

reset()が常にnoexceptであり、条件付きnoexceptではないのはなぜですか?

Tのデストラクタがnoexceptである場合、正確にnoexceptにするnoexcept(noexcept(std::declval<T>().~T()))を宣言することが可能であるべきです。私はここで何かを見逃していますか、またはこれは標準の監視ですか(これは確かに非常に学術的な状況だからです)?

32
anderas

関数オブジェクトの呼び出しの要件 Deleter は、std::unique_ptr<T>::reset()メンバーの要件にリストされているように、これに固有です。

[unique.ptr.single.modifiers]/ から、N4660年頃§23.11.1.2.5/ 3;

_unique_ptr_修飾子

void reset(pointer p = pointer()) noexcept;

必要:式get_deleter()(get())は整形式で、明確に定義された振る舞いを持ち、例外をスローしない

一般に、型は破壊可能である必要があります。そして、C++コンセプトDestructiblecppreference に従って、標準では [utility.arg.requirements]/2 、§20.5.3.1(強調鉱山);

Destructible要件

u.~T()uが所有するすべてのリソースが回収されます例外は伝播されません

置換関数の一般的なライブラリ要件にも注意してください。 [res.on.functions]/2

25
Niall

_std::unique_ptr::reset_はデストラクタを直接呼び出さず、代わりに削除テンプレートパラメータ(デフォルトは_std::default_delete<T>_)のoperator ()を呼び出します。この演算子は、で指定されているように、例外をスローしないために必要です。

23.11.1.2.5 unique_ptr修飾子[unique.ptr.single.modifiers]

void reset(pointer p = pointer()) noexcept;

必須:式get_deleter()(get())は整形式であり、>明確に定義された動作を持ち、例外をスローしません。

slow not thrownoexceptと同じではないことに注意してください。 _default_delete_のoperator ()は、noexcept演算子のみを呼び出す(deleteステートメントを実行する)場合でも、deleteとして宣言されません。そのため、これは標準ではかなり弱点のようです。 resetは、条件付きでnoexceptにする必要があります。

_noexcept(noexcept(::std::declval<D>()(::std::declval<T*>())))
_

または、削除者のoperator ()は、noexceptでなければなりません。

5
VTT

標準化委員会での議論に参加していなかったので、最初に考えたのは、標準化委員会がデストラクタをスローする苦痛を決定したケースだということです。スタック、それは価値がありませんでした。

特に_unique_ptr_については、_unique_ptr_によって保持されているオブジェクトがデストラクタをスローした場合に何が起こる可能性があるかを考慮してください。

  1. unique_ptr::reset()が呼び出されます。
  2. 内部のオブジェクトは破棄されます
  3. デストラクタがスローします
  4. スタックは巻き戻しを開始します
  5. _unique_ptr_は範囲外になります
  6. 後藤2

これを回避する方法がありました。 1つは、_unique_ptr_内のポインターを削除する前にnullptrに設定することです。これにより、メモリリークが発生したり、デストラクタが一般的なケースで例外をスローした場合の動作を定義したりします。

4
martiert

おそらくこれを例で説明する方が簡単でしょう。 resetが常にnoexceptであるとは限らないと仮定した場合、次のようなコードを記述して問題を引き起こすことができます。

_class Foobar {
public:
  ~Foobar()
  {
    // Toggle between two different types of exceptions.
    static bool s = true;
    if(s) throw std::bad_exception();
    else  throw std::invalid_argument("s");
    s = !s;
  }
};

int doStuff() {
  Foobar* a = new Foobar(); // wants to throw bad_exception.
  Foobar* b = new Foobar(); // wants to throw invalid_argument.
  std::unique_ptr<Foobar> p;
  p.reset(a);
  p.reset(b);
}
_

p.reset(b)が呼び出されたとき、何をしますか?

メモリリークを回避するため、pbの所有権を主張してインスタンスを破棄できるようにする必要がありますが、スローするaも破棄する必要があります例外。では、どのようにしてabの両方を破壊しますか?

また、doStuff()はどの例外をスローする必要がありますか? _bad_exception_または_invalid_argument_?

resetを常にnoexceptに強制すると、これらの問題を回避できます。しかし、この種のコードはコンパイル時に拒否されます。

0
OLL