std::swap()
並べ替えや割り当ての際に、多くのstdコンテナ(std::list
およびなどstd::vector
)で使用されます。
ただし、のstd実装swap()
は非常に一般化されており、カスタムタイプに対しては非効率的です。
したがってstd::swap()
、カスタムタイプ固有の実装でオーバーロードすることで効率を上げることができます。しかし、どのように実装すれば、標準コンテナで使用できますか?
std::swap()
並べ替えや割り当ての際に、多くのstdコンテナ(std::list
およびなどstd::vector
)で使用されます。
ただし、のstd実装swap()
は非常に一般化されており、カスタムタイプに対しては非効率的です。
したがってstd::swap()
、カスタムタイプ固有の実装でオーバーロードすることで効率を上げることができます。しかし、どのように実装すれば、標準コンテナで使用できますか?
回答:
スワップをオーバーロードする正しい方法は、スワップしているのと同じ名前空間にスワップを書き込むことです。これにより、引数依存ルックアップ(ADL)を介して見つけることができます。特に簡単なことは、次のとおりです。
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);
// ...
}
};
std::sort
ADLを使用して要素を交換するAは、C ++ 03に準拠していませんが、C ++ 11に準拠しています。また、クライアントが慣用的ではないコードを使用する可能性があるという事実に基づいて、なぜ-1と回答するのでしょうか。
モッツァ314
以下は、一般的な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
の名前空間、それだけで一つの方法ということ、それを行うことを忘れないでください(とティーチ他人に)するだけで簡単です。
template <>
、あなたの最初の例では、私は、出力を得るexp::swap(A, A)
GCCから。それで、なぜ専門化を好まないのですか?
using std::swap
て、「ヘッダーファイル内にステートメントを使用しない」ルールの例外はあると思いますか?実際、using std::swap
中に入れてみません<algorithm>
か?私はそれが少数の人々のコードを壊すかもしれないと思います。たぶんサポートを廃止し、最終的にそれを入れますか?
using std::swap
ヘッダー内の関数スコープに制限しようと思います。はい、swap
ほとんどキーワードです。しかし、いいえ、それはまったくキーワードではありません。したがって、本当に必要になるまで、すべての名前空間にエクスポートしないことをお勧めします。 swap
のようなものoperator==
です。最大の違いは、operator==
修飾された名前空間構文で呼び出すことさえ考えたことがないということです(それはあまりに醜いでしょう)。
(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実装は完全に使用する必要があります) -qualified std :: swap)。
ありcomp.lang.c ++のスレッドが。司会で長く話題のdicussion。ただし、そのほとんどは部分的な特殊化に関するものです(現在、これを行うには良い方法はありません)。
::swap
追加した過負荷がより特化したstd::swap
ため、過負荷vector
ので、呼び出し、後者は関連性があるの無い特殊化をキャプチャし、。それが実際の問題であるかどうかはわかりません(しかし、これが良いアイデアであると主張しているわけでもありません!)。