std::swap() をオーバーロードする方法
-
08-06-2019 - |
質問
std::swap()
多くの標準コンテナー ( std::list
そして std::vector
) 並べ替え中および均等割り当て中。
しかし、の標準実装は、 swap()
これは非常に一般化されており、カスタム タイプにとってはかなり非効率的です。
したがって、過負荷によって効率が向上する可能性があります std::swap()
カスタムタイプ固有の実装を使用します。しかし、std コンテナーで使用されるようにするには、どうすれば実装できるでしょうか?
解決
スワップをオーバーロードする正しい方法は、スワップしているものと同じ名前空間にスワップを書き込むことで、次の方法で見つけられるようになります。 引数依存の検索 (ADL). 。特に簡単にできることの 1 つは次のとおりです。
class X
{
// ...
friend void swap(X& a, X& b)
{
using std::swap; // bring in swap for built-in types
swap(a.base1, b.base1);
swap(a.base2, b.base2);
// ...
swap(a.member1, b.member1);
swap(a.member2, b.member2);
// ...
}
};
他のヒント
注目 Mozza314
ジェネリック医薬品の効果シミュレーションはこちら std::algorithm
電話をかける std::swap
, 、そしてユーザーに名前空間 std でスワップを提供してもらいます。これは実験であるため、このシミュレーションでは namespace exp
の代わりに namespace std
.
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
exp::swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
namespace exp
{
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
私の場合、これは次のように出力されます。
generic exp::swap
コンパイラーが異なるものを出力する場合は、テンプレートの「2 フェーズ ルックアップ」が正しく実装されていません。
コンパイラが (C++98/03/11 のいずれかに) 準拠している場合は、ここで示したのと同じ出力が得られます。そしてその場合、あなたが恐れていることがまさに起こります。そしてあなたの swap
名前空間に std
(exp
)それが起こるのを止められませんでした。
Dave と私は両方とも委員会のメンバーであり、10 年間にわたって標準のこの分野に取り組んできました (ただし、常にお互いに意見が一致しているわけではありません)。しかし、この問題は長い間解決されており、解決方法については私たち二人とも意見が一致しています。この分野における Dave の専門家の意見/回答は、自己責任で無視してください。
この問題は、C++98 が公開された後に明らかになりました。2001 年頃からデイブと私は、 この地域で働く. 。そして、これが最新の解決策です。
// simulate <algorithm>
#include <cstdio>
namespace exp
{
template <class T>
void
swap(T& x, T& y)
{
printf("generic exp::swap\n");
T tmp = x;
x = y;
y = tmp;
}
template <class T>
void algorithm(T* begin, T* end)
{
if (end-begin >= 2)
swap(begin[0], begin[1]);
}
}
// simulate user code which includes <algorithm>
struct A
{
};
void swap(A&, A&)
{
printf("swap(A, A)\n");
}
// exercise simulation
int main()
{
A a[2];
exp::algorithm(a, a+2);
}
出力は次のとおりです。
swap(A, A)
アップデート
次のような観察がなされています。
namespace exp
{
template <>
void swap(A&, A&)
{
printf("exp::swap(A, A)\n");
}
}
効く!それで、なぜそれを使わないのでしょうか?
あなたの場合を考えてみましょう A
はクラステンプレートです:
// simulate user code which includes <algorithm>
template <class T>
struct A
{
};
namespace exp
{
template <class T>
void swap(A<T>&, A<T>&)
{
printf("exp::swap(A, A)\n");
}
}
// exercise simulation
int main()
{
A<int> a[2];
exp::algorithm(a, a+2);
}
今ではまた機能しなくなりました。:-(
それで、あなたは置くことができます swap
名前空間 std に追加して機能させます。ただし、忘れずに置く必要があります swap
で A
テンプレートがある場合の の名前空間: A<T>
. 。そして、次のようにすると両方のケースが機能するため、 swap
で A
の名前空間を使用すると、その方法を 1 つだけ実行する方が覚えやすく (また他の人に教えるのも) 簡単です。
(C++ 標準により) std::swap をオーバーロードすることは許可されていませんが、独自の型のテンプレート特殊化を std 名前空間に追加することは特に許可されています。例えば。
namespace std
{
template<>
void swap(my_type& lhs, my_type& rhs)
{
// ... blah
}
}
そうすれば、std コンテナ (およびその他の場所) での使用では、一般的なものではなく、専門分野が選択されます。
また、スワップの基本クラス実装を提供するだけでは、派生型には十分ではないことにも注意してください。例えば。あなたが持っている場合
class Base
{
// ... stuff ...
}
class Derived : public Base
{
// ... stuff ...
}
namespace std
{
template<>
void swap(Base& lha, Base& rhs)
{
// ...
}
}
これは Base クラスでは機能しますが、2 つの派生オブジェクトを交換しようとすると、テンプレート化された交換が完全に一致するため、標準バージョンの汎用バージョンが使用されます (派生オブジェクトの「ベース」部分のみを交換するという問題が回避されます) )。
注記:前回の回答から間違った部分を削除するためにこれを更新しました。ああ!(指摘してくれたpuetzkとj_random_hackerに感謝)
一般的に std:: に何かを追加すべきではないのは正しいですが、名前空間では、ユーザー定義型のテンプレート特殊化の追加が特に許可されています。関数のオーバーロードはそうではありません。これは微妙な違いです:-)
17.4.3.1/1 C ++プログラムが、特に指定がない限り、名前空間STDまたは名前空間STDを備えた名前空間に宣言または定義を追加することは未定義です。プログラムは、標準ライブラリテンプレートのテンプレートの専門分野を名前空間STDに追加する場合があります。標準ライブラリのこのような専門化(完全または部分的)は、宣言が外部リンクのユーザー定義の名前に依存しない限り、およびテンプレートの専門化が元のテンプレートの標準ライブラリ要件を満たしていない限り、未定義の動作をもたらします。
std::swap の特殊化は次のようになります。
namespace std
{
template<>
void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}
template<> ビットがないと、許可される特殊化ではなく、未定義のオーバーロードになります。@Wilkaが提案したデフォルトの名前空間を変更するアプローチは、ユーザーコードでも機能する可能性がありますが(Koenig検索では名前空間のないバージョンが優先されるため)、機能することが保証されておらず、実際には機能することは想定されていません(STL実装は完全に使用する必要があります) -修飾された std::swap)。
があります comp.lang.c++.moderated のスレッド とともに 長さ 話題の討論。ただし、そのほとんどは部分的な特殊化に関するものです (現時点ではこれを行う良い方法はありません)。