826 Views
May 20, 15
スライド概要
第五回 Effective Modern C++ 勉強会の資料です
Item 23
サイボウズ・ラボ株式会社で教育向けのOSやCPU、コンパイラなどの研究開発をしています。
Effective Modern C++ 勉強会 #5 Item 23 内田 公太 (@uchan_nos) サイボウズ株式会社 2015/05/20
アジェンダ • Move semantics について軽く • Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう
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) }; • 実行コストの面 • コストの高いコピーに変えて、値を「移動」させる • 所有権の面 • ポインタからポインタへ、所有権を「移動」させる
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう std::move と std::forward が「何をしないか」 • std::move は何もムーブしない • std::forward は何も転送しない • この2つは実行時に何もしない。 1バイトたりとも実行コードを生成しない。 • →単にキャストするだけの関数である • std::move は無条件に引数を rvalue へキャストする • std::forward は条件付きで引数を rvalue へキャストする
Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
template<typename T>
typename remove_reference<T>::type&&
move(T&& param)
{
using ReturnType =
typename remove_reference<T>::type&&;
return static_cast<ReturnType>(param);
}
• std::move は Universal Reference を受け取り、Rvalue
Reference にキャストする
• int i; move(i);
→ T は int&
→ T&& は int&
→ remove_reference<T>::type&& は int&&
Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
template<typename T>
typename remove_reference<T>::type&&
move(T&& param)
{
using ReturnType =
typename remove_reference<T>::type&&;
C++11
return static_cast<ReturnType>(param);
}
template<typename T>
delctype(auto) move(T&& param)
{
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
}
C++14
シンプル!
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう • std::move はキャストしかしないのだから rvalue_cast などの方がより良かったかもしれない • 重要なのは std::move はキャストをするがムーブはしない ということ • std::move されたオブジェクトは rvalue となり、 普通はムーブの候補となる →例外もある(次ページ)
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう class Annocation { public: explicit Annotation(std::string text); ... }; • アノテーションを表すクラスを書く場合を考える • コンストラクタは std::string を取り、データメンバにコ ピーする • Item 41 を思い出したあなたは、値型を取ることにした
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう class Annocation { public: explicit Annotation(const std::string text); ... }; • コンストラクタは text を変更する必要がないので、 「可能ならいつでも const を付けよう」 という由緒ある習慣に従うことにした
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう class Annocation { public: explicit Annotation(const std::string text) : value(std::move(text)) { ... } ... private: std::string value; }; • コピーのコストを避けるため Item 41 に忠実に従い、 std::move を text に適用した
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう class Annocation { public: explicit Annotation(const std::string text) : value(std::move(text)) { ... } • このコードは • • • • コンパイルでき リンクでき 実行でき text の内容が value にセットされる • が、 text がムーブされることはない • ムーブではなくコピーされる
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう class string { public: ... string(const string& rhs); string(string&& rhs); ... }; • std::move(text) → const std::string&& • これは std::string のムーブコンストラクタに渡せない • が、コピーコンストラクタには渡せる • const lvalue 参照は const rvalue に束縛可能なので。 • text は rvalue にキャストされるのにコピーされる!
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう 2つの教訓 • ムーブ対象にしたいオブジェクトは const にしない • const オブジェクトに対するムーブ要求は、静かにコ ピー操作に置き換わる • std::move は実際にムーブをしないばかりか、ムー ブができる型になることさえ保証しない • rvalue になることのみが保証される
Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
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));
}
• std::forward の典型的な利用シナリオ:
他の関数に渡すための Universal reference を受け
取る関数テンプレート
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう void process(const Widget& lvalArg); void process(Widget&& rvalArg); Widget w; logAndProcess(w); logAndProcess(std::move(w)); • process は 2 種類のオーバーロードがあるので、 logAndProcess に lvalue を渡したら上が、 logAndProcess に rvalue を渡したら下が それぞれ呼ばれてほしい。
Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
void process(const Widget& lvalArg);
void process(Widget&& rvalArg);
template<typename T>
void logAndProcess(T&& param)
{
...
process(param);
}
• しかし param は lvalue なので process(param) は
lvalue なオーバーロードを呼び出してしまう
• 関数の引数はすべて lvalue である!
• param が rvalue な値で初期化されたときに限り
rvalue にキャストされる仕組みが必要
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう void logAndProcess(T&& param) Widget w; logAndProcess(w); logAndProcess(std::move(w)); // T is Widget& // T is Widget std::forward<Widget&>(param) std::forward<Widget>(param) // Widget& // Widget&& • そこで std::forward ですよ • T に param が rvalue で初期化されたかどうかがエン コードされている
Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
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));
}
• T に param が rvalue で初期化されたかどうかがエン
コードされている
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう class Widget { public: Widget(Widget&& rhs) : s(std::move(rhs.s)) class Widget { public: Widget(Widget&& rhs) : s(std::forward<std::string>(rhs.s)) • std::move と std::forward をうまく使い分けよう • 技術的には std::forward さえあれば事足りるけど。 • std::move ならタイプ数は少なく、間違った型を渡す心配もない。 • さらに重要なのは、ムーブと転送は全く異なる概念であること
Item 23: Understand std::move and std::forward. std::move と std::forward を理解しよう 覚えておくべきこと • std::move は rvalue への無条件キャストを行う。 それ自身はムーブは一切行わない。 • std::forward はその引数が rvalue に束縛されてい るときのみ rvalue へのキャストを行う。 • std::move と std::forward は実行時に何もしない。