Weitere ähnliche Inhalte
Ähnlich wie Effective modern c++ 5 (6)
Effective modern c++ 5
- 2. アジェンダ
• Move semantics について軽く
• Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
- 3. Move semantics
• 実行コストの面
• コストの高いコピーに変えて、値を「移動」させる
• 所有権の面
• ポインタからポインタへ、所有権を「移動」させる
std::unique_ptr<int> p1{ new int(41) };
std::unique_ptr<int> p2{ p1 };
std::unique_ptr<int> p1{ new int(41) };
std::unique_ptr<int> p2{ std::move(p1) };
←コンパイルエラー
- 4. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• std::move は何もムーブしない
• std::forward は何も転送しない
• この2つは実行時に何もしない。
1バイトたりとも実行コードを生成しない。
• →単にキャストするだけの関数である
• std::move は無条件に引数を rvalue へキャストする
• std::forward は条件付きで引数を rvalue へキャストする
std::move と std::forward が「何をしないか」
- 5. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• std::move は Universal Reference を受け取り、Rvalue
Reference にキャストする
• int i; move(i);
→ T は int&
→ T&& は int&
→ remove_reference<T>::type&& は int&&
template<typename T>
typename remove_reference<T>::type&&
move(T&& param)
{
using ReturnType =
typename remove_reference<T>::type&&;
return static_cast<ReturnType>(param);
}
- 6. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
template<typename T>
delctype(auto) move(T&& param)
{
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
}
template<typename T>
typename remove_reference<T>::type&&
move(T&& param)
{
using ReturnType =
typename remove_reference<T>::type&&;
return static_cast<ReturnType>(param);
}
C++11
C++14
シンプル!
- 7. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• std::move はキャストしかしないのだから
rvalue_cast などの方がより良かったかもしれない
• 重要なのは
std::move はキャストをするがムーブはしない
ということ
• std::move されたオブジェクトは rvalue となり、
普通はムーブの候補となる
→例外もある(次ページ)
- 8. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• アノテーションを表すクラスを書く場合を考える
• コンストラクタは std::string を取り、データメンバにコ
ピーする
• Item 41 を思い出したあなたは、値型を取ることにした
class Annocation {
public:
explicit Annotation(std::string text);
...
};
- 9. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• コンストラクタは text を変更する必要がないので、
「可能ならいつでも const を付けよう」
という由緒ある習慣に従うことにした
class Annocation {
public:
explicit Annotation(const std::string text);
...
};
- 10. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• コピーのコストを避けるため Item 41 に忠実に従い、
std::move を text に適用した
class Annocation {
public:
explicit Annotation(const std::string text)
: value(std::move(text))
{ ... }
...
private:
std::string value;
};
- 11. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• このコードは
• コンパイルでき
• リンクでき
• 実行でき
• text の内容が value にセットされる
• が、 text がムーブされることはない
• ムーブではなくコピーされる
class Annocation {
public:
explicit Annotation(const std::string text)
: value(std::move(text))
{ ... }
- 12. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• std::move(text) → const std::string&&
• これは std::string のムーブコンストラクタに渡せない
• が、コピーコンストラクタには渡せる
• const lvalue 参照は const rvalue に束縛可能なので。
• text は rvalue にキャストされるのにコピーされる!
class string {
public:
...
string(const string& rhs);
string(string&& rhs);
...
};
- 13. • ムーブ対象にしたいオブジェクトは const にしない
• const オブジェクトに対するムーブ要求は、静かにコ
ピー操作に置き換わる
• std::move は実際にムーブをしないばかりか、ムー
ブができる型になることさえ保証しない
• rvalue になることのみが保証される
Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
2つの教訓
- 14. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• std::forward の典型的な利用シナリオ:
他の関数に渡すための Universal reference を受け
取る関数テンプレート
void process(const Widget& lvalArg);
void process(Widget&& rvalArg);
template<typename T>
void logAndProcess(T&& param)
{
auto now =
std::chrono::system_clock::now();
makeLogEntry(“calling ‘process’”, now);
process(std::forward<T>(param));
}
- 15. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• process は 2 種類のオーバーロードがあるので、
logAndProcess に lvalue を渡したら上が、
logAndProcess に rvalue を渡したら下が
それぞれ呼ばれてほしい。
void process(const Widget& lvalArg);
void process(Widget&& rvalArg);
Widget w;
logAndProcess(w);
logAndProcess(std::move(w));
- 16. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• しかし param は lvalue なので process(param) は
lvalue なオーバーロードを呼び出してしまう
• 関数の引数はすべて lvalue である!
• param が rvalue な値で初期化されたときに限り
rvalue にキャストされる仕組みが必要
void process(const Widget& lvalArg);
void process(Widget&& rvalArg);
template<typename T>
void logAndProcess(T&& param)
{
...
process(param);
}
- 17. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• そこで std::forward ですよ
• T に param が rvalue で初期化されたかどうかがエン
コードされている
void logAndProcess(T&& param)
std::forward<Widget&>(param) // Widget&
std::forward<Widget>(param) // Widget&&
Widget w;
logAndProcess(w); // T is Widget&
logAndProcess(std::move(w)); // T is Widget
- 18. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• T に param が rvalue で初期化されたかどうかがエン
コードされている
void process(const Widget& lvalArg);
void process(Widget&& rvalArg);
template<typename T>
void logAndProcess(T&& param)
{
auto now =
std::chrono::system_clock::now();
makeLogEntry(“calling ‘process’”, now);
process(std::forward<T>(param));
}
- 19. Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• std::move と std::forward をうまく使い分けよう
• 技術的には std::forward さえあれば事足りるけど。
• std::move ならタイプ数は少なく、間違った型を渡す心配もない。
• さらに重要なのは、ムーブと転送は全く異なる概念であること
class Widget {
public:
Widget(Widget&& rhs)
: s(std::move(rhs.s))
class Widget {
public:
Widget(Widget&& rhs)
: s(std::forward<std::string>(rhs.s))
- 20. • std::move は rvalue への無条件キャストを行う。
それ自身はムーブは一切行わない。
• std::forward はその引数が rvalue に束縛されてい
るときのみ rvalue へのキャストを行う。
• std::move と std::forward は実行時に何もしない。
覚えておくべきこと
Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう