rvalue reference v3
右辺値参照(rvalue reference)の導入に伴い、コンストラクタとコピーの挙動が変わります。
#include <iostream> #include <type_traits> using namespace std; struct Noisy { Noisy() { cout << "ctor Noisy()\n"; } ~Noisy() { cout << "dtor ~Noisy()\n"; } Noisy(const Noisy&) { cout << "ctor Noisy(const Noisy&)\n"; } Noisy& operator=(const Noisy&) { cout << "copy Noisy(const Noisy&)\n"; return *this; } // -------- C++11 から、右辺値参照を引数とする コンストラクタ/コピーが追加された Noisy(Noisy&&) { cout << "ctor Noisy(Noisy&&)\n"; } Noisy& operator=(Noisy&&) { cout << "copy Noisy(Noisy&&)\n"; return *this; } }; struct NoisyInt { Noisy noisy; int n = 123; }; int main() { NoisyInt src; NoisyInt dst = move(src); dst = NoisyInt(); }
コンストラクタ/コピーの挙動をモニタするNoisyをメンバに持つNoisyIntに対し、右辺値参照を引数とするコンストラクト/コピーを行っています。VC++12(現VS2013)では
ctor Noisy() ctor Noisy(const Noisy&) ctor Noisy() copy Noisy(const Noisy&) dtor ~Noisy() dtor ~Noisy() dtor ~Noisy()
とプリントされるのに対し、Torinoでは
ctor Noisy() ctor Noisy(Noisy&&) ctor Noisy() copy Noisy(Noisy&&) dtor ~Noisy() dtor ~Noisy() dtor ~Noisy()
となります。つまり、右辺値参照を引数としたコンストラクト/コピーが行われた際、内包するメンバそれぞれに対しても(正しく)右辺値参照コンストラクト/コピーが行われます。
reference qualifier
右辺値/左辺値のどちらに対して適用されたかによって、メンバ関数を呼び分けられるようになります。メンバ関数宣言のおしりに&を付けると左辺値、&&を付けると右辺値です。
#include <iostream> using namespace std; struct X { void f() & { cout << "L-value\n"; } void f() && { cout << "R-value\n"; } }; int main() { X x; x.f(); // L-value X().f(); // R-value }
return type deduction
C++11から、関数の戻り型を後置する書式が追加されました。
// return type を後置する auto plus(int x, int y) -> int { return x + y; }
関数テンプレートだと引数しだいで戻り型がころころ変わりうるため、decltypeが導入されています。
template<typename T, typename U> auto plus(const T& t, const U& u) -> decltype(t+u) { return t + u; } ... auto x = plus(10, 4); // int x = 14 auto y = plus(string("じゅう"), "よん"); // string y = "じゅうよん"
Torinoに追加されたreturn type deductionは関数内のreturn 式;に基づいて戻り型を推論してくれるので、"-> 型"を省略できます。
template<typename T, typename U> auto plus(const T& t, const U& u) { // 戻り型を推論してくれる! return t + u; } ... auto x = plus(10, 4); // int x = 14 auto y = plus(string("じゅう"), "よん"); // string y = "じゅうよん"
ただし、これ"だけ"では期待する型を返してくれないことがあります。
template<typename Container> auto zeroth(Container& c) { return c[0]; } ... std::vector<int> vi = { 10, 20, 30 }; ++zeroth(vi); // 先頭要素をインクリメント...できない
ダメなんですね、return型から推論される型はint&ではなく、intだと解釈されてしまうのです。
この問題を解決すべく、decltype(auto)が用意されています。
template<typename Container> decltype(auto) zeroth(Container& c) { return c[0]; // Container::operator[]() の型を"そのまま"返す } ... std::vector<int> vi = { 10, 20, 30 }; ++zeroth(vi); // 先頭要素をインクリメントする!