「More C++ Idioms/throw しない swap(Non-throwing swap)」の版間の差分

削除された内容 追加された内容
Yak! (トーク | 投稿記録)
 
1 行
teb6md f81tpvn7894323ndrx
=<center>thorw しない swap(Non-throwing swap)</center>=
=== 意図 ===
* 例外安全で効率的な swap(交換)操作を実装する
* その swap 操作に対して、総称プログラミングを有効活用できるよう統一されたインタフェースを提供する。
 
=== 別名 ===
* 例外安全な swap(Exception safe swap)
 
=== 動機 ===
swap の典型的な実装は次のようになる。
<source lang="cpp">
void swap (T &a, T &b)
{
T temp (a);
a = b;
b = temp;
}
</source>
同じ型の大きく複雑なオブジェクトの swap は、中間的な一時オブジェクトのためのリソースの獲得と解放により、
非常に非効率になりうる。
また、前述の swap の実装では、リソースが利用可能ではなかった場合、例外が送出されるかもしれない。
そもそも新しいリソースの要求が必要でなかった場合には、そのような挙動は認めがたい。
 
=== 解法とサンプルコード ===
throw しない swap イディオム(Non-throwing swap idiom)は、望ましい効果を得るために[[More C++ Idioms/ハンドル・ボディ(Handle Body)|ハンドル・ボディ(Handle Body)]]イディオムを使用する。
対象の概念は、2つの実装クラスに分割される。
一つは、ハンドル(handle)であり、もう一つはボディ(body、本体)である。
ハンドルはボディ(本体)オブジェクトへのポインタを保持する。
swap は、単純なポインタの交換として実装される。
例外を送出しないことが保証され、新しいリソースが獲得も解放もされないため非常に効率的である。
<source lang="cpp">
namespace Orange {
class String
{
char * str;
public:
void swap (String &s) throw ()
{
std::swap (this->str, s.str);
}
};
}
</source>
効率的で例外安全な swap 関数は、(上述のように)メンバ関数として実装可能だが、
throw しない swap イディオムは、単純さ、整合性、そして総称プログラミングの有効活用のために
その先を行く。
std::swap の明示的特殊化を、クラス自身の名前空間だけでなく std 名前空間にも追加すべきである。
<source lang="cpp">
namespace Orange { // String の名前空間
template <>
void swap (String & s1, String & s2) throw ()
{
s1.swap (s2);
}
}
namespace std {
template <>
void swap (String & s1, String & s2) throw ()
{
s1.swap (s2);
}
}
</source>
これら 2 箇所への追加は、swap の 2 つの異なる一般的な使用法に対応する。
1 つは、被修飾の swap であり、もう一つは完全修飾の swap (例えば std::swap)である。
非修飾の swap が使用される時には、正しい swap が Koenig の名前探索により(既に定義済みのものから)発見される。
完全修飾の swap が使用される時には、Koenig の名前探索は抑止され、
std 名前空間内のものが代わりに使用される。
これはよく知られた挙動である。
残りの議論では、完全修飾の swap のみを用いる。
C++ プログラマはしばしば下記のように swap 関数を慣用的に std:: で完全修飾して呼び出すため、統一された見た目と印象を与える。
 
<source lang="cpp">
template <class T>
void zoo (T t1, T t2) {
//...
int i1, i2;
std::swap(i1,i2); // 統一性に注目
std::swap(t1,t2); // 同上
}
</source>
 
そのような場合、zoo が先に定義した String によってインスタンス化されたときに、正しい効率的な swap の実装が選択される。
さもなくば、
メンバ関数の swap と、名前空間での swap 関数を定義した目的を完全に無にして
デフォルトの std::swap 関数テンプレートがインスタンス化されるだろう。
swap の明示的特殊化を std 名前空間に定義するというこのイディオムは、特に総称プログラミングで有用である。
 
throw しない swap イディオムの使用による統一性は、下記の例のような再帰的な利用をもたらす。
<source lang="cpp">
class UserDefined
{
String str;
public:
void swap (UserDefined & u) throw ()
{
std::swap (str, u.str);
}
};
namespace std
{
// std 名前空間のテンプレートの完全特殊化は、std 名前空間に追加できる
template <>
void swap (UserDefined & u1, UserDefined & u2) throw ()
{
u1.swap (u2);
}
}
class Myclass
{
UserDefined u;
char * name;
public:
void swap (Myclass & m) throw ()
{
std::swap (u, m.u); // 統一性によるイディオムの再帰的な使用
std::swap (name, m.name); // 同上
}
}
namespace std
{
// std 名前空間のテンプレートの完全特殊化は、std 名前空間に追加できる
template <>
void swap (Myclass & m1, Myclass & m2) throw ()
{
m1.swap (m2);
}
};
</source>
 
従って、例外安全で効率的な swap の実装に従った、その型の std::swap の特殊化を定義するのは良い考えである。
現在の C++ 標準は、新しいテンプレートを std 名前空間に追加することを認めていないが、std 名前空間のテンプレートを特殊化して std 名前空間に置くことは認めている。
 
=== 警告 ===
テンプレートクラス(例:Matrix<T>)に対して throw しない swap イディオムを使用することは微妙な問題になりうる。
C++98 標準では、ユーザ定義型に対しては std::swap の完全特殊化のみ std 名前空間に定義することを認めている。
部分特殊化や、関数オーバーロードは認められていない。
テンプレートクラス(例:Matrix<T>)に対して同様の効果を得ようとする試みは、std 名前空間での std::swap のオーバーロードに帰着するが、これは規格的には未定義動作となる。
compl.lang.c++.moderated ニュースグループ
の壮大で長大な議論のスレッド
<ref>http://groups.google.ca/group/comp.lang.c++.moderated/browse_thread/thread/b396fedad7dcdc81 Namespace issues with specialized swap</ref>
で指摘している人々がいるように、
これは必ずしも理想の状態ではない。
解法は、クラスが定義されている名前空間と同じ名前空間内にオーバーロードされた swap 関数テンプレートを定義するか、
何も起こらないことを期待して未定義動作を無視するか、
次期標準での修正を待つかである。
 
=== 既知の利用 ===
Boost のスマートポインタの全て (例 boost::shared_ptr)
 
=== 関連するイディオム ===
* [[More C++ Idioms/コピーして swap(Copy-and-swap)|コピーして swap(Copy-and-swap)]]
 
=== References ===
<references/>
[[en:More C++ Idioms/Non-throwing swap]]
[[Category:{{BASEPAGENAME}}|{{SUBPAGENAME}}]]