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
staticではないのですか?明らかにメンバー変数を使用しません。friend
が入る場所ですか?サイド質問:
swap
sをnoexcept
でマークする必要がありますか?friend iter begin()
とfriend iter end()
を同じ方法で配置する必要がありますクラスの中?ここでfriend
は必要ないと思いますよね?swap
を記述する方法はいくつかありますが、他の方法よりも優れた方法もあります。ただし、時間の経過とともに、単一の定義が最も効果的であることがわかりました。 swap
関数の記述についてどのように考えるか考えてみましょう。
最初に、_std::vector<>
_のようなコンテナには、次のような単一引数のメンバー関数swap
があることがわかります。
_struct vector
{
void swap(vector&) { /* swap members */ }
};
_
当然、クラスもそうすべきですよね?まあ、そうでもない。標準ライブラリには あらゆる種類の不必要なもの があり、メンバーswap
はそのうちの1つです。どうして?続けましょう。
私たちがやるべきことは、標準的なものと、それを扱うために私たちのクラスneedsを識別することです。そして、スワッピングの標準的な方法は_std::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
}
}
_
この方法は時々機能しますが、常に機能するわけではありません。より良い方法がなければなりません。
がある! friend
関数を使用して、ADLで見つけることができます。
_namespace xyz
{
struct myclass
{
friend void swap(myclass&, myclass&);
};
}
_
何かを交換したいときは、関連付けます† _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
関数は「フレンド名インジェクション」と呼ばれるものを実行しました。この場合、コードは動作しましたas if関数が周囲のネームスペースで記述されている場合。たとえば、これらは同等の先行標準でした:
_struct foo
{
friend void bar()
{
// baz
}
};
// turned into, pre-standard:
struct foo
{
friend void bar();
};
void bar()
{
// baz
}
_
ただし、ADLが発明されたとき、これは削除されました。 friend
関数は、ADLを介してonlyで見つかります。自由な関数として使用したい場合は、そのように宣言する必要があります( これを参照 など)。しかし、lo!問題がありました。
単にstd::swap(x, y)
を使用すると、オーバーロードはneverになります。これは、明示的に「std
を見て、他には何もありません」と言っているためです。これが、2つの関数を書くことを提案した理由です。1つはADLを介して検出される関数として、もう1つは明示的な_std::
_修飾を処理する関数です。
しかし、私たちが見たように、これはすべての場合に機能するわけではなく、weい混乱に終わります。代わりに、慣用的なスワッピングは別のルートに行きました:クラスの仕事として_std::swap
_を提供する代わりに、上記のように修飾されたswap
を使用しないことを確認するのはスワッパーの仕事です。そして、これは人々がそれを知っている限り、かなりうまくいく傾向があります。しかし、そこには問題があります:資格のない呼び出しを使用する必要があるのは直感的ではありません!
これを簡単にするために、Boostのような一部のライブラリは、関連する名前空間として_boost::swap
_でswap
の非修飾呼び出しを行う関数_std::swap
_を提供しました。これは物事を再び簡潔にするのに役立ちますが、それでも残念です。
C++ 11で_std::swap
_の動作に変更がないことに注意してください。これは、私と他の人が誤ってそうだと思っていたものです。これに少し噛まれた場合、 こちらをご覧ください 。
要するに、メンバー関数は単なるノイズであり、特殊化はくて不完全ですが、friend
関数は完全で機能します。スワップするときは、_boost::swap
_または_std::swap
_が関連付けられた非修飾swap
を使用します。
†非公式には、関数呼び出し中に名前が考慮される場合、名前は関連付けです。詳細については、§3.4.2をお読みください。この場合、_std::swap
_は通常考慮されません。しかし、associate it(修飾されていないswap
によって考慮されるオーバーロードのセットに追加)して、検出できるようにします。
そのコードは次と同等です(almost in way)
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)であり、関数名が修飾されておらず、関数に名前空間スコープがある場合にのみ、クラスのフレンド宣言で関数を定義できます。
このような関数は暗黙的にインラインです。クラスで定義されたフレンド関数は、それが定義されているクラスの(字句)スコープ内にあります。クラス外で定義されたフレンド関数はそうではありません。