C++にSwap関数を書く
6444 ワード
swap関数はほとんどの初心者が書いた最も基本的な関数の一つであり、通常は関数概念、パラメータと実パラメータ、参照、ポインタを理解するために使用されます.しかし,この極めて基礎的な関数は非常に重要な役割を果たしている.その重要性,普遍性こそが,swap関数に極めて高い安全性が必要であることを意味するので,異常を投げ出さないswap関数をどのように書くかが本章で議論するテーマである.
一般的なSwap関数
まず例を見てみましょう
これは最も一般的なswap関数の実装です.
もう一つの方法はは、加算減算が使用されているため、組み込みタイプにのみ適用されます.この方法は、クラスタイプの変数には使用できません(クラスが 省スペースは卵がないと言えます.この方法は一般的に内蔵タイプにしか使われていませんが、現在のコンピュータの多くは64ビットで、内蔵タイプは最大8バイトを占めています.このメモリは現在の機械にとって何でもありません.3つの加減演算で置き換えられ、損をしません. でデータオーバーフローや精度損失が発生する可能性があります.栗: 上記のswap関数の別のバージョン:
これは、ビット演算によってswap関数を実現し、追加の記憶空間を必要としない方法です.
標準ライブラリにはデフォルトのswap関数が用意されており、予想通りに実現されています.
注:pimpl手法(pointer to implementation)は、オブジェクト実装の詳細をプライベートポインタメンバーによって隠す一般的な手法であり、インタフェースと実装を分離し、それらの間の結合関係を解除し、ファイル間のコンパイル依存性を低減することができる.
pimpl手法の栗:
目標を達成したが、
注意:これもプログラムをコンパイルして実行する場合がありますが、それらの動作は明確に定義されていませんので、stdに新しいものを追加しないでください.
クラステンプレートのswapメンバー関数を呼び出すために非メンバーswap関数を宣言することができますが、std::swap関数の特化またはリロードバージョンとして宣言することはありません.
さあ、やっとswap関数を書きました.私たちはそれを使うことができます\(>0<)/:
こんなにたくさんのswap関数の実現を議論して、自分のために適当なswap関数を見つけましょう(●'◡'●)ノ♥
本文は『Effective C++』第25条から参考する
一般的なSwap関数
まず例を見てみましょう
void swap(int a, int b)
{
int swap = a;
a = b;
b = swap;
}
これは最も一般的なエラーであり、a
およびb
はint
タイプの値であり、それらは実パラメータのコピーにすぎず、役割ドメインはswap関数内にのみ存在するため、交換の役割を果たすことができない.これは最も一般的なswap関数の実装です.
void swap(int& a, int& b)
{
int swap = a;
a = b;
b = swap;
}
参照を使用することによって、swap関数におけるパラメータの変更がswapの役割ドメイン外に伝達されることを保証する.もう一つの方法は
void swap(int& a, int& b)
{
a = a + b; // a + b
b = a - b; // a + b - b = a
a = a - b; // a + b - a = b
}
この関数は,a
とb
の一連の演算により,中間容器としての新しい変数の申請を回避し,空間を節約した.しかし、このようなやり方は本当に損得がありません.その欠点が多すぎるからです.+
、-
演算子を再ロードしない限り).int
型変数の範囲は-2^31~2^31-1
です.パラメータがint型の場合、a
とb
が2^31-1の場合、最初のステップa = a + b
でデータオーバーフローが発生します.パラメータのタイプがfloat
またはdouble
の場合、基本演算を行うと精度の損失が発生します.void swap(int* a, int* b)
{
*a = *a + *b;
*b = *a - *b;
*a = *a - *b;
}
これは、上記の3つの欠点に加えて、ポインタa
とb
が同じアドレス、すなわち別名を使用した場合、*a
が元の2倍になり、*b
が0に等しいという欠点がある.これは、ビット演算によってswap関数を実現し、追加の記憶空間を必要としない方法です.
void swap(int a, int b)
{
a = a ^ b; // a ^ b
b = a ^ b; // a ^ b ^ b = a
a = a ^ b; // a ^ b ^ a = b
}
クラスとコンテナのswap関数標準ライブラリにはデフォルトのswap関数が用意されており、予想通りに実現されています.
namespace std {
template
void swap ( T& a, T& b )
{
T c(a);
a = b;
b = c;
}
}
Tがcopyコンストラクション関数を持つ限り,このデフォルトのswap関数は動作する.しかし、小川を流すのを助けることができますが、太平洋を越えるには、少し足りません.オブジェクトに大量のデータが含まれている場合、この方法は3回の長時間の付与演算を行い、特に置換されたクラスがpimpl
の手法で実現された場合、効率が極めて低下する.注:pimpl手法(pointer to implementation)は、オブジェクト実装の詳細をプライベートポインタメンバーによって隠す一般的な手法であり、インタフェースと実装を分離し、それらの間の結合関係を解除し、ファイル間のコンパイル依存性を低減することができる.
pimpl手法の栗:
class BoxImpl
{
public: //
...
private:
string name; //
string id;
std::vector vec;
...
};
class Box
{
public:
Box(const Box& x); // copy
Box& operator=(const Box& x)
{
...
*pImpl = *(x.pImpl); // '=' , Box
...
}
...
private:
BoxImpl* pImpl; // Box
};
私たちが考えているように、2つのBox
オブジェクトを置き換えると、3つのBox
をコピーする以外に、3つのBoxImpl
をコピーするので、swap関数を実現する別の方法が必要です.特化バージョンのstd::swap
関数を生成してpImpl
ポインタだけを置き換えると(stdネーミングスペースの何も変更できませんが、標準テンプレートに特化バージョンを追加できます)、swap関数を呼び出すときにBox
オブジェクトにカスタマイズされた方法を使用します.他のオブジェクトに対してデフォルトのメソッドを使用します.namespace std {
template<>
void swap(Box& a, Box& b)
{
swap(a.pImpl, b.pImpl);
}
}
しかし、この関数はコンパイルできません.pImpl
ポインタはBox
のプライベートメンバーなので、Box
クラスにpublic
のインタフェースを追加してプライベートメンバーの呼び出しを隠す必要があります.class Box {
public:
...
void swap(Box& x)
{
using std::swap; //
swap(pImpl, x.pImpl); // pImpl
}
...
};
namespace std {
template<>
void swap(Box& a, Box& b)
{
a.swap(b);
}
}
このアプローチは、STLコンテナによるswapの実装と一致し、STLコンテナはstd::swap
の特化版によってコンテナのpublic swap
メンバー関数を呼び出し、このメンバー関数はデータを置換するポインタにすぎない.目標を達成したが、
class
ではなくclass templates
を置き換えると、swap関数が足りないことが分かった.読者はうんざりしているかもしれませんが、私たちは小さなswap関数を実現しただけで、どうしてこんなに多くのものを互換性があるのですか!!!あなたは変わって、あなたはもう昔の純潔で素朴なswap関数ではありません(TДT)しかし、効率のためにこのようにするのは価値がある...std::swap
を偏特化しましたnamespace std {
template
void swap< Box >(Box& a, Box& b)
{
a.swap(b); // ! 。
}
}
これは合理的に見えるが,C++はクラステンプレートの偏特化のみを可能にし,関数テンプレートの偏特化操作Orzをサポートしない.そこで賢いあなたはまた良いアイデアを思いつき、swap関数を再ロードしました.namespace std {
template
void swap(Box& a, Box& b)
{
a.swap(b); // !
}
}
ここで発生した問題は、stdネーミングスペースに特化したstdテンプレートを追加することができ、新しいテンプレートを追加することはできません.注意:これもプログラムをコンパイルして実行する場合がありますが、それらの動作は明確に定義されていませんので、stdに新しいものを追加しないでください.
クラステンプレートのswapメンバー関数を呼び出すために非メンバーswap関数を宣言することができますが、std::swap関数の特化またはリロードバージョンとして宣言することはありません.
Box
に関連するすべての機能をネーミングスペースBoxTool
に配置します(global
ネーミングスペースにも配置できますが、多くのクラス、テンプレート、方法などがあなたのglobal
ネーミングスペースにあふれていると、コードが優雅ではありません):namespace BoxTool {
... // BoxImpl Box
template
void swap(Box& a, Box& b)
{
a.swap(b); // std
} // std::swap
}
そこで私たちは安心してBoxオブジェクトを置き換えることができます.しかし、この方法はクラスにも当てはまるようなので、ずっとこの方法を使うほうが便利ではないでしょうか.しかし、そうではない.クラス特化されたswap関数をできるだけ多くの文脈で呼び出すには、クラスが存在するネーミングスペースに非メンバーのswap関数とstd::swap関数を追加する必要があります.さあ、やっとswap関数を書きました.私たちはそれを使うことができます\(>0<)/:
template
void doSomething(T& a, T& b)
{
...
swap(a, b);
...
}
しかしswapはどのバージョンを呼び出しますか?stdの一般化バージョン?特化std::swapバージョン?クラスが存在するネーミングスペースのswapバージョン?呆然とした(◎_◎;)Tの専属バージョンをオンにし、存在しない場合は一般的なstd::swap関数を呼び出します.template
void doSomething(T& a, T& b)
{
using std::swap; // std::swap
...
swap(a, b); // :std::swap(a, b);
...
}
C++の名前でルールを検索することで、私たちが使用しているswap関数が最も適切であることを確認します.しかし、コンパイラはやはり自分の人が好きなので、Tがいるネーミングスペースのテンプレート関数ではなくstdの特化バージョンを使用することに偏っています.こんなにたくさんのswap関数の実現を議論して、自分のために適当なswap関数を見つけましょう(●'◡'●)ノ♥
本文は『Effective C++』第25条から参考する