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


169

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では、をでマークする必要swapがありnoexceptますか?
  • C ++ 11とすると、範囲のために、私は配置する必要がありますfriend iter begin()し、friend iter end()クラス内の同じように?friendここでは必要ないと思いますよね?

範囲ベースの副次的な質問を検討してください:メンバー関数を記述し、std名前空間(24.6.5)のbegin()およびend()に範囲アクセスを残すことをお勧めします。範囲ベースは、グローバルまたはstd名前空間(§6.5.4を参照)。ただし、これらの関数が<iterator>ヘッダーの一部であるという欠点があります。これを含めない場合は、自分で作成することをお勧めします。
Vitus

2
なぜそれは静的ではないのですか- friend関数はメンバー関数ではないからです。
aschepler

回答:


175

を書くにはいくつかの方法がありますswap。しかし、時間の経過とともに、単一の定義が最もうまく機能することがわかりました。swap関数の記述についてどのように考えるかを考えてみましょう。


最初に、のようなコンテナには次のようなstd::vector<>単一引数のメンバー関数があることがわかりますswap

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

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


私たちがすべきことは、標準的なもの、およびそれを扱うためにクラス何をする必要があるかを特定することです。そして、スワッピングの標準的な方法は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関数は「フレンド名インジェクション」と呼ばれる処理を行いました。コードは、関数が周囲の名前空間に記述されているかのように動作ました。たとえば、これらは同等の先行標準でした。

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

// turned into, pre-standard:    

struct foo
{
    friend void bar();
};

void bar()
{
    // baz
}

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

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

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

これを簡単にするために、ブーストのようないくつかのライブラリが機能提供boost::swapだけに修飾されていない呼び出しを行う、swapと、std::swap関連付けられた名前空間としての。これは、物事を簡潔にするのに役立ちますが、それでもなお厄介です。

C ++ 11ではの動作に変更はないことに注意してくださいstd::swap。これは、私や他の人が誤っていると考えていた動作です。これに気づかれた場合は、こちらをお読みください


つまり、メンバー関数は単なるノイズであり、特殊化は醜く不完全ですが、friend関数は完全で機能します。そして、あなたは交換したときに、使用のいずれかboost::swapまたは修飾されていないswapstd::swap関連付けられました。


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


10
メンバー関数が単なるノイズだとは思わない。メンバー関数では、たとえばを使用できますstd::vector<std::string>().swap(someVecWithData);。これはswap、両方の引数が非const参照によって渡されるため、フリー関数では不可能です。
ildjarn 2011

3
@ildjarn:2行でできます。メンバー関数を持つことは、DRYの原則に違反します。
GManNickG 2011

4
@GMan:一方が他方に関して実装されている場合、DRYの原則は適用されません。そうでなければ誰もの実装を持つクラスを提唱しないだろうoperator=operator+operator+=、しかしはっきりと関連するクラスのものをオペレータが受け入れられる/対称性のために存在すると予想しました。同じことが私の意見ではメンバーswap+名前空間スコープにも当てはまりますswap
ildjarn

3
@GMan関数を考慮しすぎていると思います。少し知られているが、でもfunction<void(A*)> f; if(!f) { }理由だけで失敗することAを宣言operator!受け入れることfなども等しくf独自さんoperator!(可能性は低いが、発生することがありますが)。場合function<>の著者が考えた『おお、私が持っている『演算子ブール値』を、なぜ私が実装しなければならない『演算子を!』?それはDRYに違反する!』、それが致命的になります。のoperator!実装AAのコンストラクターを用意するだけで問題が発生しますfunction<...>。どちらの候補もユーザー定義の変換が必要になるためです。
ヨハネスシャウブ-litb

1
[メンバー]スワップ関数の記述についてどのように考えるかを考えてみましょう。当然、私たちのクラスも当然でしょうか?まあ、そうでもない。標準ライブラリにはあらゆる種類の不要なものが含まれており、メンバースワップもその1つです。リンクされたGotWは、メンバースワップ機能を提唱しています。
Xeverous

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

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


2
実際、フレンド関数は、標準のC ++では、囲んでいる名前空間には配置されません。以前の動作は「フレンド名インジェクション」と呼ばれていましたが、ADLに置き換えられ、最初の標準で置き換えられました。のトップを見るこの。(ただし、動作は非常に似ています。)
GManNickG '17

1
実際には同等ではありません。問題のコードは、swapADLにのみ表示されるようにします。それは囲んでいる名前空間のメンバーですが、その名前は他の名前ルックアップフォームには表示されません。編集:私は@GManが再び高速であると思います:)@ベンそれはISO C ++ではいつもそう
でした

2
@ベン:いいえ、フレンドインジェクションは標準には存在しませんでしたが、それは以前は広く使用されていたため、アイデア(およびコンパイラサポート)は引き継がれがちでしたが、技術的にはありません。friend関数はADLによってのみ検出され、friendアクセス可能なフリー関数である必要がある場合はfriend、クラス内とクラス外の通常のフリー関数宣言の両方として宣言する必要があります。この必要性は、たとえばこの回答でわかります。
GManNickG 2011

2
@towi:フレンド関数はネームスペーススコープにあるため、3つの質問すべてに対する答えが明確になります。(1)これは無料の関数であり、さらにクラスのプライベートおよび保護されたメンバーへのフレンドアクセスが可能です。(2)インスタンスでも静的でも、メンバーではありません。(3)ADLはクラス内を検索しませんが、friend関数には名前空間スコープがあるため、これは問題ありません。
Ben Voigt、2011

1
@ベン。仕様では、関数は名前空間メンバーであり、「関数には名前空間スコープがあります」という句は、関数が名前空間メンバーであると解釈できます(そのようなステートメントのコンテキストにかなり依存します)。そして、ADLにのみ表示される名前空間に名前を追加します(実際には、IIRCの一部は、名前が追加されているかどうかについて、仕様の他の部分と矛盾しています。しかし、名前に追加された非互換の宣言を検出するには、名前を追加する必要があります。名前空間なので、実際には非表示の名前追加されます(3.3.1p4の注を参照)。
Johannes Schaub-litb
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.