Rule of five
概要
C++の3つのルールと言えばクラスメンバに以下のメンバを追加しておくことを言います。
- コピーコンストラクタ
- コピー代入演算子
- デストラクタ
これにC++11上で2つを拡張すると5つのルールになるそうです。
- ムーブコンストラクタ
- ムーブ代入演算子
が、著名なブログにはより正確に言えば「Big4(と半分のルール)」といった表記もあるようです。
The Big Four(and a half)
何が便利か
- 関数で生成されたオブジェクトを代入演算子で返して、そのポインタを代入先に割り当てる。
- いくつかのアルゴリズム(例えばswap())
- 動的にコンテナを割り当てる。
このように、コピーをするとリソースが余計に必要な時に所有権の移譲すれば良いという考えでC++11
で追加された機能。
左辺値、右辺値
ムーブを調べていくと、左辺値右辺値の定義なんかが議論されていて、
簡単に言えば代入式の左と右なのですが、突き詰めると下記の引用のように色々な意見があるようです。
- 左辺値という用語は、もともと"代入式の左辺に置けるもの"を表すために作られた。しかし、全ての左辺値が代入式の左辺に置けるわけではない。※1
- 右辺値は大雑把にいうと"左辺値ではない値" ※1
- 誤解を恐れずに言えば、右辺値とは名前をもたない一時的なオブジェクトである。また、左辺値とは明示的に実態のある名前付きオブジェクトである。※2
- Only modifiable l-values can go on the left-hand-side of an assignment statement.(代入文の左側には、変更可能な左辺値のみが入ります。)※3
- An r-value is an un-named object; or, if you prefer, a temporary variable.(右辺値は名前のないオブジェクトです。または、必要に応じて一時変数を使用します。)
引用元
- ※1 ビャーネ・ストラウストラップ著 プログラミング言語C++
- ※2 右辺値参照・ムーブセマンティクス - cpprefjp C++日本語リファレンス
- ※3 L-values, r-values, expressions and types - Sticky BitsSticky Bits
※1には左辺値、専値、純右辺値に分けられるとかもっと深く検討してます。
※3から少し省略して記載↓
int a = 0;
int b = 0;
a = b++++; // NG
a = ++++b; // OK
ムーブコンストラクタ・ムーブ代入演算子
std::move()
を使って値の所有権の移譲をすること。
定義には&&
を付けた演算子の宣言で可能。
右辺値参照・ムーブセマンティクス - cpprefjp C++日本語リファレンス
class large_class
{
// ptrという名前のメンバをこのクラスの値にして比較演算子のオーバーロードなどをしているという前提。
.... // 略
// ムーブコンストラクタ
large_class(large_class&& r) noexcept
{
ptr = r.ptr; // ポインタの移譲
r.ptr = nullptr; // 元のオブジェクトはnullptr
}
// ムーブ代入演算子
large_class& operator=(large_class&& r)
{
ptr = r.ptr; // ポインタの移譲
r.ptr = nullptr; // 元のオブジェクトはnullptr
return *this;
}
.... // コピーコンストラクタ、コピー代入演算子は省略
}
int main()
{
large_class tmp{};
large_class y1{};
large_class y2{};
// コピーコンストラクタ
large_class x1(tmp);
// tmp != nullptr;
// x1 != nullptr;
// コピー代入演算子
y1 = x1;
// tmp != nullptr;
// x1 != nullptr;
// y1 != nullptr;
// ムーブコンストラクタ
large_class x2(std::move(tmp));
// tmp == nullptr; // ※1
// x2 != nullptr;
// ムーブ代入演算子
y2 = std::move(x2);
// tmp == nullptr; // ※1
// x2 == nullptr; // ※1
// y2 != nullptr;
// ※1 使える「保証がなくなった」状態。今回はnullptrを使ったが、クラス内部の定義によってnullptr以外もあり得る。
}
unique_ptr
は複数の所有権を持てないが、std::unique_ptr<int> q = std::move(p);
のように移譲することは可能。
また、以下を満たさない限り暗示的に生成される。
- クラスがコピー演算を宣言していない
- クラスがムーブ演算を宣言していない
- クラスがデストラクタを宣言していない
個人的に思うこと
std::move(p)
を記述するのは見た目的になんか分かりにくいのでp1 <- P2
とかp1 <-(P2)
みたいな演算子にならなかったのかなと。