コンテナを保持するクラスと、そのコンテナへのイテレータがあります。移動コンストラクターを正しく実装するにはどうすればよいですか? 標準では、移動後にイテレータが有効なままであることに依存できないことを思い出したようです (これは非常にばかげています)。無効化された場合などにイテレータを「更新」できる手段はありますか? または、コンテナを動的に割り当てて移動し、イテレータをそのまま有効にしておく必要がありますか?
2 に答える
更新:コンテナーのホルダーとして aを使用するstd::unique_ptr
のが標準的な一般的な解決策です。単にコンテナーを移動せず、所有権を譲渡してイテレーターを交換するだけです。あなたがすでに言ったように、これを最適化として特別なケースにすることができますが、一般的なソリューションも非常に効率的であると予想され、それが実際のものであることを証明した後、コードの複雑さ (バグの可能性) を受け入れるだけです。ユースケースのパフォーマンスを向上させます。
将来の読者のために、以前の回答を以下に残しておきます。それとコメントを読んで、他のソリューションが実際に機能しない理由と、どの場合に問題が発生するかを確認してください。
イテレータを更新する明白な方法は次のとおりです。
Container c = ...;
Container::iterator it = ...;
const auto d = std::distance( c.begin(), it );
Container n = std::move(c);
it = n.begin();
std::advance( it, d );
これは一般に線形ですが、反復子がランダム アクセス反復子の場合は一定です。
おそらくそれをしたくないので、役立つはずの2つのオプションがあります。デフォルトで新しいコンテナを構築しswap
、イテレータを無効にせずに使用するか、コンテナを a に入れstd::unique_ptr
て代わりに移動します。
最初のアプローチ ( swap
) では、両方のインスタンスがコンテナー インスタンスを持つ必要があり、これは、 内に格納された単純な単一のポインターよりも少し大きくなる可能性がありますstd::unique_ptr
。インスタンスを頻繁に移動する場合は、std::unique_ptr
ベースのアプローチが望ましいように思えますが、アクセスごとにもう 1 つのポインター間接化が必要になります。自分のケースに何が最も適しているかを自分で判断 (および測定) します。
イテレータの無効化に対する暗黙の保証は、move ctor にも当てはまると思います。つまり、以下はすべてのコンテナで機能するはずですが、次のとおりですstd::array
。
template<class Container>
struct foo_base
{
Container c;
Container::iterator i;
foo_base(foo_base&& rhs, bool is_end)
: c( std::move(rhs.c) )
, i( get_init(is_end, rhs.i) )
{}
Container::iterator get_init(bool is_end, Container::iterator ri)
{
using std::end; // enable ADL
return is_end ? end(c) : ri;
}
};
template<class Container>
struct foo : private foo_base<Container>
{
foo(foo&& rhs)
: foo_base(std::move(rhs), rhs.i == end(rhs.c))
{}
};
アロケータが移動代入のために伝播しない場合、移動に移動代入が必要ないため、基本クラスを介した複雑な初期化が必要です。end()
イテレータが無効化される可能性があるため、イテレータのチェックが必要です。このチェックは、コンテナーを移動する前に実行する必要があります。ただし、アロケーターが伝播することを確認できる場合 (または、move-assignment がケースの反復子を無効にしない場合)、以下のより単純なバージョンを使用してswap
、 を move-assignment に置き換えることができます。
注: この関数の唯一の目的は、get_init
ADL を有効にすることです。ADL を無効にfoo_base
するメンバー関数がある可能性があります。using 宣言end
は、可能性のあるメンバー関数を見つけるために非修飾ルックアップを停止するため、ADL は常に実行されます。ここで ADL を失うことに慣れている場合は、 を使用して削除することもできます。std::end(c)
get_init
move ctor に対するそのような暗黙の保証がないことが判明した場合でも、 に対する明示的な保証はまだありswap
ます。これには、次を使用できます。
template<class Container>
struct foo
{
Container c;
Container::iterator i;
foo(foo&& rhs)
{
using std::end; // enable ADL
bool const is_end = (rhs.i == end(rhs.c));
c.swap( rhs.c );
i = get_init(is_end, rhs.i);
}
Container::iterator get_init(bool is_end, Container::iterator ri)
{
using std::end; // enable ADL
return is_end ? end(c) : ri;
}
};
ただし、スワップには [container.requirements.general]/7+8 で定義されているいくつかの要件があります。
スワップされるオブジェクトが等しいか、 [...]に属する
swap
アロケーターを持っていない限り、コンテナーの関数への呼び出しの動作は未定義です。. がの場合、 andのアロケータも non-member への非修飾呼び出しを使用して交換されます。それ以外の場合、それらは交換されません。動作は未定義 です。allocator_traits<allocator_type>::propagate_on_container_swap::value
true
Compare
Pred
Hash
a
b
swap
allocator_traits<allocator_type>::propagate_on_container_swap::value
true
a
b
swap
a.get_allocator() == b.get_allocator()
つまり、両方のコンテナーが等しいアロケーターを持つ必要があります (必須ではありません)。
ムーブ コンストラクション OTOH では、例外がスローされないことのみが必要です (アロケーター対応コンテナーの場合)。アロケータは常に移動されます。