最近の質問 (特にそれに対する私の答え)に不思議に思いました:
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()))
を宣言することが可能であるべきです。私はここで何かを見逃していますか、またはこれは標準の監視ですか(これは確かに非常に学術的な状況だからです)?
関数オブジェクトの呼び出しの要件 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++コンセプトDestructibleの cppreference に従って、標準では [utility.arg.requirements]/2 、§20.5.3.1(強調鉱山);
Destructible
要件
u.~T()
u
が所有するすべてのリソースが回収されます例外は伝播されません。
置換関数の一般的なライブラリ要件にも注意してください。 [res.on.functions]/2 。
_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 throwはnoexcept
と同じではないことに注意してください。 _default_delete
_のoperator ()
は、noexcept
演算子のみを呼び出す(delete
ステートメントを実行する)場合でも、delete
として宣言されません。そのため、これは標準ではかなり弱点のようです。 reset
は、条件付きでnoexceptにする必要があります。
_noexcept(noexcept(::std::declval<D>()(::std::declval<T*>())))
_
または、削除者のoperator ()
は、noexcept
でなければなりません。
標準化委員会での議論に参加していなかったので、最初に考えたのは、標準化委員会がデストラクタをスローする苦痛を決定したケースだということです。スタック、それは価値がありませんでした。
特に_unique_ptr
_については、_unique_ptr
_によって保持されているオブジェクトがデストラクタをスローした場合に何が起こる可能性があるかを考慮してください。
unique_ptr::reset()
が呼び出されます。unique_ptr
_は範囲外になりますこれを回避する方法がありました。 1つは、_unique_ptr
_内のポインターを削除する前にnullptr
に設定することです。これにより、メモリリークが発生したり、デストラクタが一般的なケースで例外をスローした場合の動作を定義したりします。
おそらくこれを例で説明する方が簡単でしょう。 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)
が呼び出されたとき、何をしますか?
メモリリークを回避するため、p
はb
の所有権を主張してインスタンスを破棄できるようにする必要がありますが、スローするa
も破棄する必要があります例外。では、どのようにしてa
とb
の両方を破壊しますか?
また、doStuff()
はどの例外をスローする必要がありますか? _bad_exception
_または_invalid_argument
_?
reset
を常にnoexcept
に強制すると、これらの問題を回避できます。しかし、この種のコードはコンパイル時に拒否されます。