web-dev-qa-db-ja.com

パブリックフレンドスワップメンバー機能

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ではないのですか?明らかにメンバー変数を使用しません。
  • 「swapを適切に使用すると、ADL経由でswapが検出されます」? ADLは名前空間を検索しますか?しかし、それはクラスの内部にも見えますか?それとも、friendが入る場所ですか?

サイド質問:

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

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によって考慮されるオーバーロードのセットに追加)して、検出できるようにします。

158
GManNickG

そのコードは次と同等です(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)であり、関数名が修飾されておらず、関数に名前空間スコープがある場合にのみ、クラスのフレンド宣言で関数を定義できます。

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

7
Ben Voigt