私の理解では、たとえばベクター内で再割り当てするときに、コンパイラーがそれらを利用できるようにするには、move-constructors と move-assign を noexcept とマークする必要があります。
しかし、move-assign、move-construct が実際にスローする可能性がある実際のケースはありますか?
更新:
たとえば、構築時にリソースが割り当てられているクラスは、非スロー移動できません。
私の理解では、たとえばベクター内で再割り当てするときに、コンパイラーがそれらを利用できるようにするには、move-constructors と move-assign を noexcept とマークする必要があります。
しかし、move-assign、move-construct が実際にスローする可能性がある実際のケースはありますか?
更新:
たとえば、構築時にリソースが割り当てられているクラスは、非スロー移動できません。
しかし、move-assign、move-construct (または swap) が実際にスローする可能性がある実際のケースはありますか?
はい。の実装を考えてみましょうstd::list
。end
反復子は、リスト内の「最後の要素の 1 つ後ろ」を指す必要があります。動的に割り当てられたノードが指すstd::list
場所の実装が存在します。end
デフォルトのコンストラクターでさえ、そのようなノードを割り当てて、 を呼び出すときにend()
何かを指すようにします。
このような実装では、すべてend()
のコンストラクターが指すノードを割り当てる必要があります。移動コンストラクターも同様です。その割り当ては失敗し、例外がスローされる可能性があります。
この同じ動作は、任意のノードベースのコンテナーに拡張できます。
「短い文字列」の最適化を行うこれらのノードベースのコンテナーの実装もあります。これらは、動的に割り当てるのではなく、コンテナー クラス自体にエンド ノードを埋め込みます。したがって、デフォルト コンストラクター (およびムーブ コンストラクター) は何も割り当てる必要はありません。
移動代入演算子はcontainer<X>
、コンテナーのアロケーターpropagate_on_container_move_assignment::value
が false で、lhs のアロケーターが rhs のアロケーターと等しくない場合にスローできます。その場合、move 代入演算子はメモリの所有権を rhs から lhs に移すことを禁じられています。std::allocator
を使用している場合、 のすべてのインスタンスstd::allocator
が互いに等しいため、これは発生しません。
アップデート
propagate_on_container_move_assignment::value
が falseの場合の準拠した移植可能な例を次に示します。VS、gcc、およびclangの最新バージョンに対してテストされています。
#include <cassert>
#include <cstddef>
#include <iostream>
#include <vector>
template <class T>
class allocator
{
int id_;
public:
using value_type = T;
allocator(int id) noexcept : id_(id) {}
template <class U> allocator(allocator<U> const& u) noexcept : id_(u.id_) {}
value_type*
allocate(std::size_t n)
{
return static_cast<value_type*>(::operator new (n*sizeof(value_type)));
}
void
deallocate(value_type* p, std::size_t) noexcept
{
::operator delete(p);
}
template <class U, class V>
friend
bool
operator==(allocator<U> const& x, allocator<V> const& y) noexcept
{
return x.id_ == y.id_;
}
};
template <class T, class U>
bool
operator!=(allocator<T> const& x, allocator<U> const& y) noexcept
{
return !(x == y);
}
template <class T> using vector = std::vector<T, allocator<T>>;
struct A
{
static bool time_to_throw;
A() = default;
A(const A&) {if (time_to_throw) throw 1;}
A& operator=(const A&) {if (time_to_throw) throw 1; return *this;}
};
bool A::time_to_throw = false;
int
main()
{
vector<A> v1(5, A{}, allocator<A>{1});
vector<A> v2(allocator<A>{2});
v2 = std::move(v1);
try
{
A::time_to_throw = true;
v1 = std::move(v2);
assert(false);
}
catch (int i)
{
std::cout << i << '\n';
}
}
このプログラムは次を出力します。
1
これは、が false で、問題の 2 つのアロケーターが等しくないvector<T, A>
場合に、移動代入演算子がその要素をコピー/移動していることを示します。propagate_on_container_move_assignment::value
これらのコピー/ムーブのいずれかがスローされると、コンテナーのムーブ割り当てがスローされます。
はい、投げ技のコンストラクターは実際に存在します。noexcept-movable がstd::pair<T, U>
どこにあり、コピーのみ可能かを検討してください(コピーがスローできると仮定します)。次に、スローされる可能性のある便利な移動コンストラクターがあります。T
U
std::pair<T, U>
std::move_if_noexcept
必要に応じて、標準ライブラリにユーティリティがあります (std::vector::resize
少なくとも基本的な例外保証を実装すると便利です)。
Move コンストラクターと強力な例外保証も参照してください。