196

copy-and-swap-idiomに対する美しい答えには、少し助けが必要なコードがあります。

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        using std::swap; 
        swap(first.mSize, second.mSize); 
        swap(first.mArray, second.mArray);
    }
    // ...
};

そして彼はメモを追加します

std::swap を型に特化し、フリー関数スワップと一緒にクラス内スワップを提供する必要があるという主張は他にもあります。 、そして私たちの機能はADLを通して見つけられます。1つの機能で十分です。

friend私は「不親切」な言葉を少し使っているので、認めなければなりません。だから、私の主な質問は次のとおりです。

  • 無料の関数のように見えますが、クラス本体の中にありますか?
  • なぜこれはswap静的ではないのですか? 明らかに、メンバー変数は使用しません。
  • 「スワップを適切に使用すると、ADL を介してスワップが検出されます」 ? ADL は名前空間を検索しますよね? しかし、それはクラスの中にも見えますか? それとも入ってくるのはここfriendですか?

補足質問:

  • C++11 では、swaps を でマークする必要がありnoexceptますか?
  • C++11 とそのrange-forfriend iter begin()では、friend iter end()同じようにクラス内に配置する必要がありますか? ここでは必要ないと思いますfriendよね?
4

2 に答える 2

198

を書くswapにはいくつかの方法があり、いくつかは他よりも優れています。しかし、時間の経過とともに、単一の定義が最も効果的であることがわかりました。swap関数の書き方について考えてみましょう。


std::vector<>最初に、次のような単一引数のメンバー function を持つコンテナーを確認しswapます。

struct vector
{
    void swap(vector&) { /* swap members */ }
};

当然、私たちのクラスもそうすべきですよね?まあ、そうではありません。標準ライブラリにはあらゆる種類の不要なものがあり、メンバーswapはその 1 つです。なんで?続けましょう。


私たちがしなければならないことは、正規のものと、それを扱うためにクラスが何をする必要があるかを特定することです。そして、スワッピングの標準的な方法は withstd::swapです。これが、メンバー関数が役に立たない理由です。一般に、それらは物事を交換する方法ではなく、の動作には関係ありませんstd::swap

それでは、std::swap仕事をするためにはstd::vector<>、 の専門化を提供する必要があります (提供する必要がありました) std::swap

namespace std
{
    template <> // important! specialization in std is OK, overloading is UB
    void swap(myclass&, myclass&)
    {
        // swap
    }
}

この場合、それは確かに機能しますが、明らかな問題があります。関数の特殊化は部分的ではありません。つまり、これでテンプレート クラスを特殊化することはできず、特定のインスタンス化のみを行うことができます。

namespace std
{
    template <typename T>
    void swap<T>(myclass<T>&, myclass<T>&) // error! no partial specialization
    {
        // swap
    }
}

この方法は、場合によっては機能しますが、常に機能するわけではありません。もっと良い方法があるはずです。


がある!関数を使用して、 ADLfriendを介して見つけることができます。

namespace xyz
{
    struct myclass
    {
        friend void swap(myclass&, myclass&);
    };
}

何かを交換したいときは、†</sup>を関連付けてからstd::swap、非修飾呼び出しを行います。

using std::swap; // allow use of std::swap...
swap(x, y); // ...but select overloads, first

// that is, if swap(x, y) finds a better match, via ADL, it
// will use that instead; otherwise it falls back to std::swap

friend関数とは この辺りは混乱しています。

C++ が標準化される前は、friend関数は「フレンド名インジェクション」と呼ばれる処理を行っていました。これは、関数が周囲の名前空間に記述されているかのようにコードが動作するというものでした。たとえば、これらは標準化前と同等でした。

struct foo
{
    friend void bar()
    {
        // baz
    }
};

// turned into, pre-standard:    

struct foo
{
    friend void bar();
};

void bar()
{
    // baz
}

ただし、ADLが発明されたとき、これは削除されました。このfriend関数は、ADL を介してのみ見つけることができます。フリー関数として使用したい場合は、そのように宣言する必要がありました (たとえば、this を参照してください)。しかし、見よ!問題がありました。

を使用するだけではstd::swap(x, y)、オーバーロードは決して見つかりません。これは、明示的に「 を見てstd、他には何もない」と言ったためです。これが、一部の人々が 2 つの関数を作成することを提案した理由です。1 つはADLを介して検出される関数として、もう 1 つは明示的なstd::修飾を処理するためのものです。

しかし、これまで見てきたように、これはすべての場合に機能するわけではなく、最終的には厄介な問題が発生します。代わりに、慣用的なスワッピングは別のルートに行きました: クラスの仕事を提供する代わりに、上記のように、std::swap修飾されたを使用しないことを確認するのはスワッパーの仕事swapです。そして、人々がそれについて知っている限り、これはかなりうまくいく傾向があります. しかし、そこに問題があります。修飾されていない呼び出しを使用する必要があるのは直感的ではありません!

これを簡単にするために、Boost などの一部のライブラリboost::swapは、 への非修飾呼び出しを行うだけの関数 をswapstd::swap関連付けられた名前空間として提供しました。これにより、物事が再び簡潔になりますが、それでも残念です。

C++11 では の動作に変更がないことに注意してくださいstd::swap。これに噛まれた場合は、ここを読んでください


要するに、メンバー関数は単なるノイズであり、特殊化は見苦しく不完全ですが、friend関数は完全で機能します。そして、スワップするときは、関連付けられた使用または非修飾のいずれかを使用しますboost::swapswapstd::swap


†非公式に、関数呼び出し中に名前が考慮される場合、名前は関連付けられます。詳細については、§3.4.2 を参照してください。この場合、std::swap通常は考慮されません。しかし、それを関連付けて ( unqualified によって考慮されるオーバーロードのセットに追加してswap)、見つけられるようにすることができます。

于 2011-04-17T19:24:24.227 に答える
7

そのコードは(ほぼすべての点で)次のものと同等です。

class dumb_array
{
public:
    // ...
    friend void swap(dumb_array& first, dumb_array& second);
    // ...
};

inline void swap(dumb_array& first, dumb_array& second) // nothrow
{
    using std::swap; 
    swap(first.mSize, second.mSize); 
    swap(first.mArray, second.mArray);
}

クラス内で定義されたフレンド関数は次のとおりです。

  • 囲んでいる名前空間に配置
  • 自動的inline
  • それ以上の資格なしでクラスの静的メンバーを参照することができます

正確なルールはセクション[class.friend]にあります(C ++ 0xドラフトのパラグラフ6と7を引用します):

クラスが非ローカルクラス(9.8)であり、関数名が修飾されておらず、関数に名前空間スコープがある場合にのみ、クラスのフレンド宣言で関数を定義できます。

このような関数は暗黙的にインラインです。クラスで定義されたフレンド関数は、それが定義されているクラスの(字句)スコープにあります。クラスの外部で定義されたフレンド関数はそうではありません。

于 2011-04-17T18:41:20.073 に答える