std::swap()
は、並べ替えや代入時にも多くの std コンテナー ( や など) で使用されstd::list
ます。std::vector
しかし、 の std 実装swap()
は非常に一般化されており、カスタム型に対してはかなり非効率的です。
したがってstd::swap()
、カスタム型固有の実装でオーバーロードすることで効率を高めることができます。しかし、std コンテナーで使用されるように実装するにはどうすればよいでしょうか?
std::swap()
は、並べ替えや代入時にも多くの std コンテナー ( や など) で使用されstd::list
ます。std::vector
しかし、 の std 実装swap()
は非常に一般化されており、カスタム型に対してはかなり非効率的です。
したがってstd::swap()
、カスタム型固有の実装でオーバーロードすることで効率を高めることができます。しかし、std コンテナーで使用されるように実装するにはどうすればよいでしょうか?
の実装をオーバーロードする (別名、それを特殊化する) 正しい方法は、スワップするものと同じ名前空間に書き込むことです。これにより、引数依存ルックアップ (ADL)std::swap
で見つけることができます。特に簡単にできることは次のとおりです。
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 年間取り組んできました (常に意見が一致しているわけではありません)。しかし、この問題は長い間解決されており、解決方法については両者で一致しています。あなた自身の責任で、この分野でのデイブの専門家の意見/回答を無視してください。
この問題は、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 に入れて機能させることができます。ただし、 template: がある場合は、 の名前空間swap
を入れることを忘れないでください。そして、の名前空間を入れればどちらの場合も機能するので、その 1 つの方法で行う方が覚えやすく (そして他の人に教えやすく) なります。A
A<T>
swap
A
(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)
{
// ...
}
}
これは基本クラスで機能しますが、2 つの派生オブジェクトをスワップしようとすると、テンプレート化されたスワップが完全に一致するため、std のジェネリック バージョンが使用されます (派生オブジェクトの「基本」部分のみをスワップするという問題が回避されます)。 )。
注:これを更新して、最後の回答から間違ったビットを削除しました。ああ!(指摘してくれた 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に、トピックの長い議論を伴うスレッドがあります。ただし、そのほとんどは部分的な特殊化に関するものです(現在、これを行うための適切な方法はありません)。