本当は怖いHPC

HPC屋の趣味&実益ブログ

Effective Modern C++勉強会#3 : ATNDに参加して、Item12について発表してきました。

Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14

Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14

発表資料はこちら。

www.slideshare.net

話の中で登場した、reference qualifierを使って定義できる左辺値オーバーロードと右辺値オーバーロードについて再度まとめておく。

左辺値オーバーロード(lvalue overload)と右辺値オーバーロード(rvalue overload)とは、オブジェクトのメンバ関数が呼び出されるとき、*thisが右辺値であるか左辺値であるかによってメンバ関数を呼び分けることができる機能。右辺値と左辺値については解説しているブログが沢山あるが、非常に簡単に言ってしまうと「使い捨ての値が右辺値、それ以外の普通の変数は左辺値」ということだ。

次のようなコード例となる。

// test.cpp
#include <iostream>

class MyClass {
 public:
  void foo() &  { std::cout << "lvalue overload" << std::endl; }
  void foo() && { std::cout << "rvalue overload" << std::endl; }
};

int main() {
  MyClass m;  // 左辺値
  m.foo();

  MyClass().foo(); // 右辺値

  return 0;
}

// コンパイル&実行
// $ g++ --version
//g++ (GCC) 4.9.2
//Copyright (C) 2014 Free Software Foundation, Inc.
//This is free software; see the source for copying conditions.  There is NO
//warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// $ g++ -std=c++11 test.cpp
// $ ./a.out
// lvalue overload
// rvalue overload

で、これがどのように役に立つのかというと、右辺値文脈で呼ばれたときにある種の最適化を行うことができる。例えば、*thisが使い捨ての値である事を利用して、データのコピーを省略するなど。

#include <iostream>
#include <vector>

class Widget {
 public:
  using DataType = std::vector<double>;

  DataType &data() & {
    std::cout << "data() &  {...} is called" << std::endl;
    return values;
  }
  DataType data() && {
    std::cout << "data() && {...} is called" << std::endl;
    return std::move(values);
  }

 private:
  DataType values;
};

Widget makeWidget() {
  return std::move(Widget());
}

int main() {
  // lvalue overloadが呼ばれる。Vectorのコピーが行われる
  Widget w;
  auto vals1 = w.data();

  // rvalue overloadが呼ばれる。値のコピーが省略できる
  auto vals2 = makeWidget().data();
}

// 実行結果:
// $ g++ -std=c++11 test.cpp
// $ ./a.out
// data() &  {...} is called
// data() && {...} is called

左辺値オーバーロードは、従来の関数定義に他ならないので、従来の関数定義と衝突するような左辺値オーバーロードは定義できない。

class Widget {
 public:
  void foo() { }
  void foo() & { }
};

// ==>
// g++ -c -std=c++11 test.cpp
//test.cpp:5:8: error: 'void Widget::foo() &' cannot be overloaded
//   void foo() & { }
//        ^
//test.cpp:4:8: error: with 'void Widget::foo()'
//   void foo() { }
//        ^

よって、実際に書くのは従来の関数定義と右辺値オーバーロード、という組み合わせになると思われる。ただし、&記号も関数シグネチャの一部なので、従来の関数定義を左辺値オーバーロードでオーバーライドすることはできない(ややこしい・・・)

【広告】