SlideShare a Scribd company logo
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 は何も転送しない
• この2つは実行時に何もしない。
1バイトたりとも実行コードを生成しない。
• →単にキャストするだけの関数である
• std::move は無条件に引数を rvalue へキャストする
• std::forward は条件付きで引数を rvalue へキャストする
std::move と std::forward が「何をしないか」
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);
}
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
シンプル!
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 を理解しよう
• アノテーションを表すクラスを書く場合を考える
• コンストラクタは std::string を取り、データメンバにコ
ピーする
• Item 41 を思い出したあなたは、値型を取ることにした
class Annocation {
public:
explicit Annotation(std::string text);
...
};
Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
• コンストラクタは text を変更する必要がないので、
「可能ならいつでも const を付けよう」
という由緒ある習慣に従うことにした
class Annocation {
public:
explicit Annotation(const std::string text);
...
};
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;
};
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))
{ ... }
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);
...
};
• ムーブ対象にしたいオブジェクトは const にしない
• const オブジェクトに対するムーブ要求は、静かにコ
ピー操作に置き換わる
• std::move は実際にムーブをしないばかりか、ムー
ブができる型になることさえ保証しない
• rvalue になることのみが保証される
Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう
2つの教訓
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));
}
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));
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);
}
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
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));
}
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))
• std::move は rvalue への無条件キャストを行う。
それ自身はムーブは一切行わない。
• std::forward はその引数が rvalue に束縛されてい
るときのみ rvalue へのキャストを行う。
• std::move と std::forward は実行時に何もしない。
覚えておくべきこと
Item 23: Understand std::move and std::forward.
std::move と std::forward を理解しよう

More Related Content

Effective modern c++ 5

  • 1. Effective Modern C++ 勉強会 #5 Item 23 内田 公太 (@uchan_nos) サイボウズ株式会社 2015/05/20
  • 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 を理解しよう