昨夜は眠れず、考え始めましたstd::swap
。おなじみのC++98バージョンは次のとおりです。
template <typename T>
void swap(T& a, T& b)
{
T c(a);
a = b;
b = c;
}
ユーザー定義クラスFoo
が外部リソースを使用する場合、これは非効率的です。一般的なイディオムは、のメソッドvoid Foo::swap(Foo& other)
と特殊化を提供することですstd::swap<Foo>
。関数テンプレートを部分的に特殊化することはできず、名前空間での名前のオーバーロードは違法であるため、これはクラステンプレートでは機能しないことに注意してください。std
解決策は、自分の名前空間にテンプレート関数を記述し、引数に依存するルックアップに依存してそれを見つけることです。これは、クライアントが直接using std::swap
呼び出すのではなく、「イディオム」に従うかどうかに大きく依存しますstd::swap
。非常にもろい。
C ++ 0xではFoo
、ユーザー定義のムーブコンストラクターとムーブ代入演算子がある場合、カスタムswap
メソッドとstd::swap<Foo>
特殊化を提供してもstd::swap
、コピーの代わりに効率的なムーブを使用するため、パフォーマンス上の利点はほとんどありません。
#include <utility>
template <typename T>
void swap(T& a, T& b)
{
T c(std::move(a));
a = std::move(b);
b = std::move(c);
}
もういじる必要がないことswap
は、プログラマーからすでに多くの負担を取り除きます。現在のコンパイラーはまだムーブコンストラクターとムーブ代入演算子を自動的に生成しませんが、私が知る限り、これは変更されます。残っている唯一の問題は例外安全性です。これは、一般に、移動操作がスローを許可されており、これによりワームの缶全体が開かれるためです。質問「移動したオブジェクトの状態は正確には何ですか?」物事をさらに複雑にします。
それから私は考えていました、std::swap
すべてがうまくいく場合、C ++ 0xのセマンティクスは正確には何ですか?スワップ前後のオブジェクトの状態はどうなっていますか?通常、移動操作によるスワッピングは外部リソースには影響せず、「フラットな」オブジェクト表現自体にのみ影響します。
swap
では、それを正確に実行するテンプレートを単純に作成してみませんか。オブジェクト表現を交換しますか?
#include <cstring>
template <typename T>
void swap(T& a, T& b)
{
unsigned char c[sizeof(T)];
memcpy( c, &a, sizeof(T));
memcpy(&a, &b, sizeof(T));
memcpy(&b, c, sizeof(T));
}
これは、それが得るのと同じくらい効率的です:それは単に生のメモリを爆破します。ユーザーの介入は必要ありません。特別なスワップメソッドや移動操作を定義する必要はありません。これは、C ++ 98でも機能することを意味します(右辺値の参照はありません)。しかし、さらに重要なことは、スローが発生しないため、例外安全性の問題を忘れることができるということです。memcpy
このアプローチには2つの潜在的な問題があります。
まず、すべてのオブジェクトが交換されることを意図しているわけではありません。クラスデザイナがコピーコンストラクタまたはコピー代入演算子を非表示にした場合、クラスのオブジェクトを交換しようとすると、コンパイル時に失敗するはずです。タイプでコピーと割り当てが合法であるかどうかをチェックするデッドコードを簡単に紹介できます。
template <typename T>
void swap(T& a, T& b)
{
if (false) // dead code, never executed
{
T c(a); // copy-constructible?
a = b; // assignable?
}
unsigned char c[sizeof(T)];
std::memcpy( c, &a, sizeof(T));
std::memcpy(&a, &b, sizeof(T));
std::memcpy(&b, c, sizeof(T));
}
どんなまともなコンパイラでも、死んだコードを簡単に取り除くことができます。(「スワップ適合性」をチェックするためのより良い方法はおそらくありますが、それは重要ではありません。重要なのはそれが可能であるということです)。
次に、一部のタイプは、コピーコンストラクターおよびコピー割り当て演算子で「異常な」アクションを実行する場合があります。たとえば、変更をオブザーバーに通知する場合があります。このような種類のオブジェクトは、そもそもコピー操作を提供するべきではなかったので、これは小さな問題だと思います。
このスワッピングのアプローチについてどう思うか教えてください。それは実際に機能しますか?使ってみませんか?これが壊れてしまうライブラリの種類を特定できますか?追加の問題がありますか?議論!