29

バックグラウンド

この質問については、次のコードを検討してください。

#include <utility>

namespace ns
{
    struct foo
    {
        foo() : i(0) {}
        int i;
        
    private:
        foo(const foo&); // not defined,
        foo& operator=(const foo&); // non-copyable
    };
    
    void swap(foo& lhs, foo& rhs)
    {
        std::swap(lhs.i, rhs.i);
    }
}

template <typename T>
void do_swap(T& lhs, T& rhs); // implementation to be determined

int main()
{
    ns::foo a, b;
    do_swap(a, b);
}

C++03 では、この実装はdo_swap「壊れている」と見なされます。

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    std::swap(lhs, rhs);
}

を明示的に指定することで、引数依存のルックアップによる検索をstd::禁止します。(許可されていないa をコピーしようとns::swapするため、コンパイルに失敗します。)代わりに、次のようにします。std::swapfoo

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    using std::swap; // allow std::swap as a backup if ADL fails to find a swap
    swap(lhs, rhs); // unqualified call to swap, allow ADL to operate
}

Nowns::swapが見つかり、std::swapあまり専門化されていないため、使用されません。それは醜いですが、機能し、後から考えると理解できます。boost::swapこれをうまくまとめます (そして、配列のオーバーロードを提供します):

#include <boost/swap.hpp>

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    boost::swap(lhs, rhs); // internally does what do_swap did above
}

質問

したがって、私の質問はstd::swap次のとおりboost::swapです。そうでない場合、なぜですか?

私には、そうすべきであることは明らかです。変更によって壊れたコードは、おそらく最初は非常に薄っぺらなものでした ( や のようなアルゴリズムとコンテナーはstd::sort十分std::vectorに指定されていませんでした。実装は、ADL スワップを呼び出すかどうかを不確定に呼び出すことができませんでした)。さらに、std::swapが配列に対して定義されるようになったため、まったく変更することは問題外ではありません。

ただし、§17.6.3.2 ではswap、標準ライブラリ内へのすべての呼び出しはstd::修飾なしで行う必要があると指定されていますが (上記のアルゴリズムとコンテナーに関する問題を修正しています)、それstd::swap自体には触れていません。を含む値を交換する例も示しますusing std::swap;。同様に、§20.2.2 (std::swap指定されている場所) は ADL について一言も述べていません。

最後に、GCC は実装で ADL を有効にしませんstd::swap(MSVC も有効にしませんが、それはあまり意味がありません)。std::swapしたがって、の動作を引き継ぐのは間違っているに違いありませboost::swapんが、変更が行われなかった理由がわかりません。:(そして私は一人じゃない

4

4 に答える 4

30

提案されていれば、概念実証の実装に反対票を投じなければならなかったでしょう。次のコードが壊れてしまうのではないかと心配しています。このコードは、過去 10 年間に少なくとも 1 回か 2 回は実際に目にしたことがあると確信しています。

namespace oops
{

    struct foo
    {
        foo() : i(0) {}
        int i;

        void swap(foo& x) {std::swap(*this, x);}
    };

    void swap(foo& lhs, foo& rhs)
    {
        lhs.swap(rhs);
    }

}

上記のコードが良いコードであるか悪いコードであるかに関係なく、作成者が C++98/03 で意図したとおりに機能するため、黙ってそれを破るハードルはかなり高いです。C++11 ではもう書く必要がないことをユーザーに伝えることはusing std::swap;、上記のコードを暗黙のうちに無限再帰に変えるという欠点を補うのに十分な利点ではありません。

書くことから抜け出す別の方法は、代わりusing std::swap;に使用することです:std::iter_swap

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    std::iter_swap(&lhs, &rhs); // internally does what do_swap did above
}
于 2012-02-16T21:05:46.630 に答える
3

概念実証の実装は次のとおりです。

#include <utility>

// exposition implementation
namespace std_
{
    namespace detail
    {
        // actual fallback implementation
        template <typename T>
        void swap(T& lhs, T& rhs)
        {
            T temp = std::move(lhs);
            lhs = std::move(rhs);
            rhs = std::move(temp);
        }
    }

    template <typename T>
    void swap(T& lhs, T& rhs)
    {
        using detail::swap; // shadows std_::swap, stops recursion
        swap(lhs, rhs); // unqualified call, allows ADL
    }
}

namespace ns
{
    struct foo
    {
        foo() : i(0) {}
        int i;

    private:
        foo(const foo&); // not defined,
        foo& operator=(const foo&); // non-copyable
    };

    void swap(foo& lhs, foo& rhs)
    {
        std::swap(lhs.i, rhs.i);
    }
}


int main()
{
    int i = 0, j = 0;
    std_::swap(i, j);

    ns::foo a, b;
    std_::swap(a, b);
}
于 2012-02-07T03:04:17.167 に答える
1

さて、にboost::swap()派遣しstd::swap()ます。それにstd::swap()似た何かをするためboost::swap()には、どこかに委任する必要があります。これはどこか他の場所で何ですか?swap()この規格は、実際の実装を提供する別のバージョンを義務付けていません。これは可能ですが、標準では義務付けられていません。

なぜそれがしないのですか?この実装を提案する提案は見当たりませんでした。もし誰かがこれをやりたいと思っていたら、私はそれが提案されたと確信しています。

于 2012-02-07T02:50:46.117 に答える