const rvalue をムーブ元にする

C++11 で追加された機能の内で重要なもののひとつがムーブセマンティクスだ。 言語のコアに追加されたのは参照の区別であり、それがあると値を複製せずに移動 (所有権の移動) だけで済ませてしまえる場合があるので性能的に有利というものである。

さて、所有権を移動させるということは移動元を破壊してしまう。 関数の返却値を const にすると破壊するわけにいかず、ムーブが機能しない。 故に関数の返却値は基本的に const は付けない方がよいとする意見を見た。

つまり'+', '-', '*', '/'の演算子オーバーロードですが、C++11ではconst返ししないほうがいいということでした。

理由はconstを付けてしまうとムーブセマンティクスが働かないからです。

http://fa11enprince.hatenablog.com/entry/2014/11/25/004307

しかし、一方で const を付けないと奇妙な式が通ってしまうということもある。

ちなみにconstを付けないとどうなるかというと上記のコードを使って次のように書けてしまいます。

int main()
{
    Foo foo1(10, 20, 30);
    Foo foo2(20, 10, 30);
    Foo foo3(40, 10, 30);

    +foo1 = foo2;         // foo1は変わらない
    (foo1 + foo2) = foo3; // 当然foo1, foo2は変わらない

    return 0;
}

上のはおかしいんだけどコンパイルが通ってしまう…。

こういう弊害がありますがこんな変な書き方することはないのでconstはなくてもいいんじゃないかなー

http://fa11enprince.hatenablog.com/entry/2014/11/25/004307

ムーブセマンティクスを機能させ、通したくないコードを禁止させる方法はある。 それは mutable を用いることだ。

#include <iostream>
#include <ostream>

class vec2 {
  friend std::ostream& operator<<(std::ostream& os, const vec2& v);
private:
  mutable int *x, *y;
public:
  vec2(int x, int y);
  vec2(const vec2& a);
  vec2(const vec2&& a);
  ~vec2(void);
  vec2& operator=(const vec2&& v);
  vec2& operator=(const vec2& v);
  const vec2 operator-(void) const;
  const vec2 operator+(void) const;
  const vec2 operator+(const vec2& v) const;
};

namespace std {
template<> constexpr typename std::remove_reference<const vec2>::type&&
move(const vec2& t ) noexcept=delete;
}

vec2::vec2(int x, int y) : x(new int(x)), y(new int(y)) {}

vec2::vec2(const vec2& a) : x(new int(*a.x)), y(new int(*a.y)) {
  std::cerr << "constructor:" << a << std::endl;
}

vec2::vec2(const vec2&& a) : x(a.x), y(a.y) {
  std::cerr << "move constructor:" << a << std::endl;
  a.x=a.y=nullptr;
}

vec2::~vec2(void) {
  delete x;
  delete y;
}

vec2& vec2::operator=(const vec2&& v) {
  std::cerr << "move assign:" << v << std::endl;
  delete x;
  delete y;
  x=new int(*v.x);
  y=new int(*v.y);
  return *this;
}

vec2& vec2::operator=(const vec2& v) {
  std::cerr << "assign:" << v << std::endl;
  delete x;
  delete y;
  x=new int(*v.x);
  y=new int(*v.y);
  return *this;
}

const vec2 vec2::operator-(void) const {
  return vec2(-(*this->x), -(*this->y));
}

const vec2 vec2::operator+(void) const {
  return vec2(*this->x, *this->y);
}

const vec2 vec2::operator+(const vec2& v) const {
  return vec2(*(this->x)+*(v.x), *(this->x)+*(v.x));
}

std::ostream& operator<<(std::ostream& os, const vec2& v) {
  return os << '(' << *v.x << ',' << *v.y << ')';
}

int main(void) {
  vec2 foo1(10, 20);
  vec2 foo2(20, 10);
  vec2 foo3(40, 10);

  +foo1 = foo2;         // エラーになる
  (foo1 + foo2) = foo3; // エラーになる
  (foo1 = foo2) = foo3; // 普通に代入する
  const vec2 foo4 = foo1+foo2; // ムーブする
  const vec2 foo5 = std::move(foo4); // const lvalue をムーブ元にするのは禁止しているのでエラー
  const vec2 foo6 = std::move(foo1); // ムーブする
  return 0;
}

const rvalue をムーブ元にしてもムーブの直後にどうせ解体されるので const 性に影響はないと思うのだけれど、どうだろう。

そして mutable キーワードは使いどころのないものと思われがちだったけれど、ムーブセマンティクスと組合わせられる場面は結構有りそうに思う。

Document ID: a5a21da1cf3fb3c1a97c7a3ffc455515