1. ホーム
  2. c++

[解決済み】パブリックフレンドスワップメンバー関数

2022-04-14 02:23:21

質問

の美しい答えの中に コピーアンドスワップイディオム があるのですが、ちょっと助けて欲しいコードがあります。

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 を私たちの型に特化させるべき、自由関数の swap と共にクラス内 swap を提供すべき、などの主張があります。swapの適切な使用は非限定呼び出しによるものであり、我々の関数はADLによって発見されるのである。一つの関数で十分なのだ。

friend 私は、正直言って、少し不親切な条件です。そこで、主な質問をします。

  • フリーファンクションに見える しかし、それはクラスボディの中にあるのですか?
  • なぜ、この swap 静的 ? 明らかにメンバ変数を使用していません。
  • swapの適切な使用は、ADLを介してswapを見つけることができます。 ? ADLは名前空間を検索しますよね?でも、クラスの中も調べるのでしょうか?それとも、ここで friend が入ってくるのでしょうか?

サイドクエスチョン

  • C++11では swap を使用しています。 noexcept ?
  • C++11とその レンジフォー を配置する必要があります。 friend iter begin()friend iter end() は、クラス内でも同じように使えるのでしょうか?私は friend はここでは必要ありませんよね?

どのように解決するのですか?

を記述する方法はいくつかあります。 swap その中には、他のものより優れているものもあります。しかし、時が経つにつれて、一つの定義が最も効果的であることが分かってきた。では、このような場合、どのように考えればよいのでしょうか。 swap という関数があります。


のようなコンテナがあることを最初に確認します。 std::vector<> は、単一引数のメンバ関数 swap のようなものです。

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

当然、私たちのクラスもそうでなければなりませんね?でも、そうでもないんです。標準ライブラリには 不要なものばかり というメンバー swap はその一つです。なぜ?続けてみましょう。


私たちがすべきことは、何がカノニカルなのか、そして私たちのクラスの が必要です。 と連携するために必要です。そしてスワッピングの正統的な方法は 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 関数は、その後 のみ 自由な関数として使用したい場合は、そのように宣言する必要があります ( これを見て といった具合に)。しかし、問題がありました。

を使うだけなら std::swap(x, y) を使用すると、オーバーロードは 決して を探すと明示的に言っているので、見つからないのです。 std そして、他のどこにもないのです。このため、2つの関数を書くことを提案する人もいました。 エーディエル を処理するためのもので、もう一つは明示的な std:: の資格を取得することができます。

しかし、これまで見てきたように、これはすべてのケースでうまくいくわけではなく、結局は醜い混乱を招くことになります。その代わりに、イディオマティックスワップは別の方法をとりました。 std::swap を使わないようにするのはスワッパーの仕事です。 swap 上記のように そしてこれは、人々がそれを知っている限り、かなりうまくいく傾向があります。しかし、そこに問題があります。非限定的な呼び出しを使う必要があるというのは、直感的ではありません

これを簡単にするために、Boost のようないくつかのライブラリは、関数 boost::swap への非限定的な呼び出しを行うだけです。 swap とは異なり std::swap を関連する名前空間として使用します。これでまた物事を簡潔にすることができますが、それでも残念なことです。

の動作は、C++11でも変更されていないことに注意してください。 std::swap と、私や他の人が勘違いしていた。もし、これに食いついたのなら ここを読む .


要するに、メンバ関数はただのノイズで、特殊化は醜く、不完全なものだが friend 関数は完全で、機能します。そしてスワップするときは boost::swap または非限定的な swapstd::swap を連想させる。


†Informally, a name is 関連 は、関数呼び出しの際に考慮される場合。詳しくは、§3.4.2を読んでください。この場合 std::swap 通常は考慮されません。 関連付ける によって考慮されるオーバーロードのセットに追加されます。 swap というように、検索されるようになります。