読者です 読者をやめる 読者になる 読者になる

とくにあぶなくないRiSKのブログ

危ないRiSKのブログだったかもしれない。本当はRiSKだけどググラビリティとか取得できるIDの都合でsscriskも使ったり。

C++11 Idioms

問題

こんなデータメンバを持つクラスのコンストラクタをどう書くでしょうか?

#include<string>
#include<vector>
#include<cstddef>
class Book {
 std::string title;
 std::vector<std::string> authors;
 std::string pub;
 std::size_t pub_year;
};

C++03では?

もう誰も使っていない古のC++03では、以下のようなコンストラクタになると思います。T ではなく、T const & でコピーのオーバーヘッドを極力減らします。

class Book {
 std::string title;
 std::vector<std::string> authors;
 std::string pub;
 std::size_t pub_year;
public:
 Book(std::string const & title,
      std::vector<std::string> const & authors,
      std::string const & pub,
      std::size_t pub_year)
  : title(title),
    authors(authors),
    pub(pub),
    pub_year(pub_year)
 {}
};

C++11では?(ダメな例)

ムーブを使えば、もっとオーバーヘッドを減らせる可能性があります。コンストラクタはどうなるでしょうか?

T && で受け取る
class Book {
 std::string title;
 std::vector<std::string> authors;
 std::string pub;
 std::size_t pub_year;
public:
 Book(std::string && title,
      std::vector<std::string> && authors,
      std::string && pub,
      std::size_t pub_year)
  : title(std::move(title)),
    authors(std::move(authors)),
    pub(std::move(pub)),
    pub_year(pub_year)
 {}
};

これだと rvalue で初期化でき、またその効率はよいのですが、lvalue で初期化することできません。これだとまずいです。

T const & と T && の両方で

コンストラクタのオーバーロードを用いて、T const & と T&& の両方を受け取れるようにします。

class Book {
 std::string title;
 std::vector<std::string> authors;
 std::string pub;
 std::size_t pub_year;
public:
 Book(std::string const & title,
      std::vector<std::string> const & authors,
      std::string const & pub,
      std::size_t pub_year)
  : title(title),
    authors(authors),
    pub(pub),
    pub_year(pub_year)
 {}
 Book(std::string && title,
      std::vector<std::string> const & authors,
      std::string const & pub,
      std::size_t pub_year)
  : title(std::move(title)),
    authors(authors),
    pub(pub),
    pub_year(pub_year)
 {}
 Book(std::string const & title,
      std::vector<std::string> && authors,
      std::string const & pub,
      std::size_t pub_year)
  : title(title),
    authors(std::move(authors)),
    pub(pub),
    pub_year(pub_year)
 {}
 Book(std::string const & title,
      std::vector<std::string> const & authors,
      std::string && pub,
      std::size_t pub_year)
  : title(title),
    authors(authors),
    pub(std::move(pub)),
    pub_year(pub_year)
 {}
 
 // MANY MANY ctor definition...
 //  :
 
 Book(std::string && title,
      std::vector<std::string> && authors,
      std::string && pub,
      std::size_t pub_year)
  : title(std::move(title)),
    authors(std::move(authors)),
    pub(std::move(pub)),
    pub_year(pub_year)
 {}
};

あまりにも長くなるので省略しました。T const & と T && の両方に対応する引数の数が N 個あるとき、コンストラクタのオーバーロードの数は 2N になります。数が爆発するのでやってられません。

解法。pass-by-value. T で受け取る

よさげな解法は T つまり値で受け取るコンストラクタを定義することです。C++03 では効率が悪いとされていた値渡しがムーブと組み合わせることで化けます。

class Book {
 std::string title;
 std::vector<std::string> authors;
 std::string pub;
 std::size_t pub_year;
public:
 Book(std::string title,
      std::vector<std::string> authors,
      std::string pub,
      std::size_t pub_year)
  : title(std::move(title)),
    authors(std::move(authors)),
    pub(std::move(pub)),
    pub_year(pub_year)
 {}
};

こうすると、rvalue でも lvalue でも初期化することができます。しかも、効率もよいです。rvalue が渡された場合、std::string 自体や std::vector<> 自体がムーブに対応しているので、ムーブ2回を経て効率よく Book を初期化することができます。lvalue が渡された場合、コピーとムーブが行われます。

pass-by-valueの問題点

  • ムーブにも小さいながらオーバーヘッドがあります
  • ムーブに対応していない型があります(std::array, std::complexなど)

ネタ元

C++ Truths: C++11 Idioms @ 2012 Silicon Valley Code Camp Idiom #1


このエントリは限られた自分なりの理解で書いたので間違いや不十分があると思います。詳細はスライドを見てください。