web-dev-qa-db-ja.com

iter_swap()とswap()-違いは何ですか?

MSDNによると

swapは、下位互換性のためにC++標準に含まれているiter_swapよりも優先して使用する必要があります。

しかし comp.std.c ++は言う

ほとんどのSTLアルゴリズムは、反復子の範囲で動作します。したがって、これらの範囲内で要素を交換するときはiter_swapを使用するのが理にかなっています。これはその意図された目的です。2つの反復子が指す要素を交換するためです。これにより、std::listなどのノードベースのシーケンスを最適化できます。これにより、データが実際にスワップされるのではなく、ノードが再リンクされるだけです。

どちらが正しいですか? iter_swapを使用する必要がありますか、それともswapを使用する必要がありますか? (iter_swapは下位互換性のためだけですか?)なぜですか?

42
user541686

標準自体には、_iter_swap_についての言及はほとんどありません。

  • swap(*a, *b)の効果があるはずですが、そのように実装する必要があるという規定はありません。
  • 逆参照された値_*a_および_*b_は「スワップ可能」である必要があります。これは、swap(*a, *b)が有効である必要があることを意味します。したがって、逆参照された型は同一である必要がありますが、イテレータ型にはすることが。
  • _iter_swap_の実装では、_std::reverse_を使用する必要があります。そのような要件は他のアルゴリズムには課されていないので、これは奇妙に思えます。

借りる seheが見つけたものSGI docs =

厳密に言えば、_iter_swap_は冗長です。これは技術的な理由でのみ存在します。状況によっては、swap(*a, *b)の解釈に必要な型の推定を実行するのが難しいコンパイラもあります。

これらはすべて、過去の遺物であることを示唆しているようです。

27
Rufflewind

これは、インターネットが多数の競合する情報を生成するシナリオの1つであるようです。

[C++11: 25.3.3/5]の下の標準では、iter_swap(a,b)の結果はswap(*a,*b)である(「abは逆参照可能である」、「*a*bと交換可能である」必要がある)とだけ述べられています。これは一見MSDNの解釈と相関します。

ただし、Microsoftはas-ifルールの考慮を怠っていたと思います。これにより、特定の場合(例:リンクリストの要素)でiter_swapswapより高速にすることができます。

したがって、comp.std.c++の引用は、2つのうち技術的に正確であると信じています。

そうは言っても、実行できる最適化にはかなり厳しい制限があります。たとえば、要素の値を物理的に交換するのではなく、単にノードを再リンクする、リンクされたリスト要素に対するiter_swapの実装を検討してください。これは有効な実装ですnotiter_swapの監視可能な動作とswapの一致は違反しています。

したがって、実際にはswapよりもiter_swapを優先することの利点はほとんどないので、固執することをお勧めします後者は単純さと一貫性のためです。 C++ 11の移動セマンティクスは、とにかく多くの場合、swapを簡単にする必要があります。

重要な違いを見つけました。

swap(*a, *b)は、*aを指すすべてのポインターをリセットして、*bの内容を指すようにするグローバル関数です。その逆も同様です。これは古いtmp = aa = bb = tmpスワップです。

iter_swapは、反復される基になるオブジェクトを変更して、それらが含まれている構造に影響を与える、より限定されたケースです。 *a*bが同じリンクリストの一部である場合、iter_swapは単にリスト内の位置を入れ替えるだけで十分でした。これは利点リスト内のオブジェクトへの外部ポインタを無効化/変更せずにリストを単純にソートしたい場合に使用します。 userオブジェクトへのポインターがある場合、listオブジェクトのuserを並べ替えてもかまわないので、誰が "現在のユーザーを変更するため、リストの並べ替えにはswapを使用しない方が良いでしょう。

5
Old Pro

法律を注意深く読む:

20.2.2スワップ[utility.swap]

  • テンプレートvoidswap(T&a、T&b)noexcept(is_nothrow_move_constructible :: value &&
    is_nothrow_move_assignable :: value); 2要件:タイプTはMoveConstructibleおよびMoveAssignableでなければなりません。 (表20)および(表22)3効果:2つの場所に格納された値を交換します。
  • テンプレートvoid swap(T(&a)[N]、T(&b)[N])noexcept(noexcept(swap(* a、* b))); 4必要条件:a [i]は、[0、N)の範囲内のすべてのiについてb [i]と交換可能でなければなりません。 (17.6.3.2)5効果:swap_ranges(a、a + N、b)

25.3.3スワップ[alg.swap]

  • テンプレートvoiditer_swap(ForwardIterator1 a、ForwardIterator2 b); 5効果:swap(* a、* b)。 6必要条件:aおよびbは参照解除可能でなければなりません。 * aは* bと交換可能です。 (17.6.3.2)

したがって、iter_swapは、2つの逆参照された場所または逆参照された場所の範囲に格納された値を交換する必要があり、参照または場所自体を交換しようとすると、適合性と戦います。これは明らかに、std :: iter_swapの背後にある理由である最適化の推測を無効にします。代わりに、Potatoswatterが適切に指摘していたように、カプセル化と抽象化がその存在の主な理由です。 std :: iter_swapとstd :: swapはそれぞれ異なる抽象化レイヤーに属します。これは、std :: swap自体と、オーバーロード解決によって選択された「swap」という名前のバイナリ非メンバー関数が異なるのと同じです。

同じ結果を達成することを理解するために開発者と設計者の役割を入れ替えても、「基本型からtypedefを宣言することはコンパイラにとっては単なるノイズであり、読者にとってはノイズではない」のように、同じであるとは限りません。冗談だと思いますが、C++全体は、Cをラップする非推奨のアーティファクトであると言えます。どちらも最下位レベルで同じことを行うため、ラッパーを使用して別のコードブロックからの抽象化を表すコードブロックを使用する場合も同様です。特に、std :: iter_swap、 "swap"、std :: swapの場合のように線が非常に細い場合。たぶん、「using std :: swap」には数個の文字しかなく、コンパイルすると消えますが、識別子を挿入して過負荷解決メカニズム全体を構築することを意味します。何度も注入され、何度も構築され、何度も交換され、何度も廃棄されました。抽象的で、カプセル化され、リサイクルされたアプローチからはほど遠い。

上層の内部作業を露出させると、メンテナンスが失敗する可能性があります。スワップドメインでは、深いメタプログラミングの包含設計で「using std :: swap」が欠落している(または混乱している)と、テンプレート関数内でサイレントに待機し、簡単に交換可能な基本型またはc配列型がビルドを中断するのを待機します。幸運なことに、あるいは無限再帰によるStackOverflow(TM)さえも。明らかに、拡張可能なメカニズムの実装を公開する必要がありますが、尊重する必要もあります。自明なスワップ可能性については、moveconstructibleとmoveassignableは、オーバーロードされたスワップ解決フックがなくても、それ自体のタイプに対してスワップ可能であることに注意してください。実際、不要なスワップ可能動作を無効にするためのあいまいな手法があります。

そのことを念頭に置いて、std :: iter_swap識別子自体の不適切な解釈ですべてを再開できる可能性があります。これは「イテレータスワッピング」ではなく、「イテレータスワッピング」を表します。引数が順方向イテレータであるという標準要件にだまされないでください。本質的に、ポインタはランダムアクセスイテレータであるため、要件を満たします。物理的にはポインタを渡し、論理的にはイテレータを渡します。委員会は通常、施設が定義された期待される動作で動作するための最小要件を指定しようとしますが、それ以上のものはありません。 「反復可能なスワッピング」の名前は、メカニズムの目標と能力を正しく表しています。 「std :: iter_swap」識別子は、生成された混乱によるものではないようですが、変更して、依存しているすべてのコードベースを元に戻すには遅すぎます。

それが機能している限り、uは好きなだけ交換してかまいませんが、私の時計には入れないでください。抽象化レイヤーを混在させてもコンパイラーは泣きませんが、インターフェースはクールすぎて避けられません。代わりに、今後のガイダンスに役立つスニペットを次に示します。

//#include <utility> // std::swap is not required here
#include <algorithm> // std::iter_swap is

namespace linker {

    class base {
    };

    class member {
    };

    template<class M = member, class B = base> // requires swappable base and member
    class link : B {
    public:
        void swap(link &other) { // using iterable swapping
            std::iter_swap(static_cast<B*>(this), static_cast<B*>(&other));
            std::iter_swap(&_member, &other._member);
        }
    private:
        M _member;
    };

    template<class base, class member>
    void swap(link<base,member>& left, link<base,member>& right) { // extending iterable swapping
        left.swap(right);
    }

}

namespace processor {

    template<class A, class B>
    void process(A &a, B &b) { // using iterable swapping
        std::iter_swap(&a, &b);
    }

}

int main() {
#if !defined(PLEASE_NO_WEIRDNESS)
    typedef
        linker::link<
            linker::link<
                linker::link< int[1] >,
                linker::link< void*, linker::link<> >
            >[2],
            linker::link<
                linker::member[3]
            >
        >
    swappable[4]; // just an array of n-ary hierarchies
#else
    typedef linker::link<> swappable;
#endif
    swappable a, b;
    processor::process(a, b);
}

追加のガイダンスとしてのいくつかの興味深いポイント:

  • スワッピングとは、例外がスローされることを意味します。声明はばかげているように見えますが、スワップイディオムがパフォーマンスではなく、極端な安全性と堅牢性に焦点を合わせていることを知ったのは一度もありません。

  • std :: iter_swapは、メタプログラミングの多くの見過ごされがちな機能の1つを紹介します。テンプレートは、オーバーロード解決だけでなく名前空間解決も行い、未知の無関係な名前空間のチェーンの最初のものとして使用できるようにします。おかげで、心配することが1つ少なくなりました。

  • スワップ可能な要件により、uはstd :: swapを直接使用できます。ただし、両方のターゲットが基本型またはc配列から基本型であると想定できる場合に限り、コンパイラは過負荷の解決をバイパスできます。悲しいことに、それはほとんどすべてのテンプレートのパラメータを除外します。 std :: swapを直接使用すると、両方のターゲットが同じタイプである(または同じタイプであることが強制される)ことを意味します。

  • リンクテンプレートクラスのように、すでにそれ自体と簡単にスワップ可能であるタイプでスワップ可能機能を宣言することに労力を無駄にしないでください(linker :: swapを削除してみてください、動作はまったく変わりません)。
    「スワップ」は、さまざまなタイプからスワップできるように設計されています。
    同じタイプの場合は自動。タイプが「交換可能」ではないこと、または
    それ自体では「スワップ不能」ですが、「スワップ可能」または
    別のタイプの「スワップ不可」。

最後に、何人の読者が気付くのだろうか

  • 20.2.2スワップ[utility.swap]

  • 25.3.3スワップ[alg.swap]

ユーティリティはアルゴリズムではないことを認識してください。特にMicrosoft-Dinkumwareの実装では、std :: iter_swapは便宜上間違ったヘッダーに存在するだけで、間違いはありません。多分それだけの識別子です。


編集:関連するいくつかの間違いに直面した後、次のように要約することができます:アルゴリズムは非常に一般的で具体的な概念であり、誰かがそれらの1つを専門化しようとするたびに、デザイナーはどこか他の場所で叫びます。 std :: iter_swapの場合、コミッティは明らかに自由を与えないので、投機の再リンクのようにアルゴリズムを微調整しようとする試みは、異なる意味と識別子に値するでしょう。また、コンテナを見逃した人には、最適化が適用されるスワップメンバー機能があるかもしれません。

最終的なレイヤーオブジェクトが非データ、基本、または非表示の重いオブジェクトを表すように、リファクタリングを改善します(十分に重い場合はストリーミングされます)。リソースの取得は初期化( [〜#〜] raii [〜#〜] )であり、new-deleteオーバーロードとコンテナアロケータの両方に se があり、trueを解き放つ必要があることを受け入れます追加の労力なしでメリットを交換します。リソースを最適化して、readquisitionでのみデータを移動し、C++がデフォルトで簡単、安全、高速に型を設計できるようにします。

モットー:昔、人々はメモリ上では太すぎ、ディスク上では遅すぎるデータに苦労していました。現在、イテレーターベクトルはストレージプールからフィルター処理され、ストリーミングされて並列パイプで処理されます。明日は一人で運転します。 PostItに値する。

0
tadevt

あなたはどちらを使うべきですか?何に使用するかによります。スワップはオブジェクトに対してのみ機能するため、2つの独立した整数または文字列または倍精度浮動小数点数を交換します。しかし、iter_swapは配列とリストに対してうまく機能し、 cplusplus.com に示されているように、2つの異なるリストの数値を交換できます。

0