Loading presentation...

Present Remotely

Send the link below via email or IM

Copy

Present to your audience

Start remote presentation

  • Invited audience members will follow you as you navigate and present
  • People invited to a presentation do not need a Prezi account
  • This link expires 10 minutes after you close the presentation
  • A maximum of 30 users can follow your presentation
  • Learn more about this feature in our knowledge base article

Do you really want to delete this prezi?

Neither you, nor the coeditors you shared it with will be able to recover it again.

DeleteCancel

Make your likes visible on Facebook?

Connect your Facebook account to Prezi and let your likes appear on your timeline.
You can change this under Settings & Account at any time.

No, thanks

rvalue reference

歌舞伎座.tech #2
by

Minoru Nishie

on 14 November 2013

Comments (0)

Please log in to add your comment.

Report abuse

Transcript of rvalue reference

rvalue reference
2013/11/14 歌舞伎座.tech #2
西江 みのる
名前:西江 みのる
所属:キテラス 第3開発セクション

自己紹介など・・・
PS Vita ニコニコ
Wii U ニコニコ
さて・・・
みなさん C++11 の機能使ってますか?
私はつかってません
なぜ C++ 11 を使わないか
バリバリの C++03 です
コンパイラのサポートが不安。
ライブラリが枯れていない。
既存のコードにどういう影響を与えるかわからない。
言語はつかいなれたものを使い続けたい
新しい機能が追加されるのは良いこと
よりよい仕様に変更されることも良いこと
よいことだけど・・・・
実際の導入にあたっては
どういう
新しい要素
が追加されて
どういう
素晴らしいプログラミング
が出来るようになったか、ということよりも、
なにが
変わっていなく
て、どういう影響を
与えなく
て、
どうすれば
少しずつ
導入していけるか、
ということのほうがずっと重視される。
そういう視点で見た時
C++11 はかなり C++ です。(?)
(どこかの言語のように)バージョンが上がったら以前のコードの意味がかわるというようなことはほとんどなく、
C++03 の考え方で大体 C++11 として正しいコードが書けます。

誰もが言語に詳しいわけでもないわけで、
普通のプログラマが考えなければならないことは増えていません。
(わからないもんは使わなければいい)

C++11 は少しづつ、
段階的に導入できるようになっています。
※ ただし rvalue reference をのぞく。
rvalue reference とは
rvalue
rvalue とは
変数として確保はされていないが処理中に生まれては消えていく
一時オブジェクトのこと。
// こういう関数があるとして
void
Function (
const
string& param );
// const char* で呼べる
void
foo( ) {
Function(
"the quick brown fox..."
);
}
どこかに string のオブジェクトができて渡されているはず
従来の C++ ではこれを const 参照で受けていた。
これが rvalue
右参照 は rvalue への referenec
// こういう関数があるとして
int
Function1();

int&&
p1 = Function1();
int&&
p2 = 123;
int&&
p3 = 123 + 123;
// int&& を返すこともできる
int&&
Function2();

int&&
x1 = Function2();

// これはエラー
// int&& x2 = p1;

int
i = 10;
int&&
x3 = std::move( i );
戻り値として生成される int をうける
リテラルは rvalue
演算結果の一時オブジェクト
右参照 は rvalue で初期化できる。
C++03 の考え方
reference は変数を参照するもの。
だけど、
変数には入っていない一時オブジェクトを参照させたい。
参照される変数がどこにあるかわからないので
変更はしないことにしよう。
一時オブジェクトは
const 参照で受ける
変更できない
C++11 の考え方
reference は変数を参照するとは限らない。
変数を参照する時と
一時オブジェクトを参照するときは型を分けよう。
参照先が一時オブジェクトであることがわかってるなら
ぶっ壊してもよくね?
一時オブジェクトは
右参照で受ける
ぶっ壊していい
右参照の導入によって
一時オブジェクトというものに対する考え方が
全く違うものになりました。
なにが変わるの?
rvalue reference とは
一時オブジェクトへの参照
転じて
破壊しても良いオブジェクトへの参照
右参照によって
変数の破壊を前提とするコードが(言語レベルで)サポートされるようになりました。
それが
move semantics
という考え方です。
右参照
rvalue reference
rvalue reference は何度も書くには長すぎるので、『右参照』と記述することにします。
『右参照』と書いて『あーるばりゅーりふぁれんす』と読みます。

一方通行みたいなもんです。
アクセラレータ
Move Semantics
// このようなクラス
class
Object{
public:

private:
strnig value;
};
さて、
以下の様なクラスがあるとして
Object::value を
文字列のコピーと行うことなく設定
する
メンバ関数を作ることはできるでしょうか。
呼び出し元の変数ぶっ壊して良ければ C++03 でも可能です。
// value を設定する。引数 v はぶっ壊すので注意
void
Object::SetValue(
string& v
){

this
->
value.swap( v )
;
}

void
PiyoPiyo() {
Object obj;
string x = ....
// どこかにある文字列


// obj.value を設定、x は破壊される、以後アクセスしない。
obj.SetValue( x );
}
ぶっちゃけていってしまえば
Move Semantics というのは・・・
これのことです
受け取った引数を手元にある変数と swap してしまう。

自分は引数の内容を受け取ることができる。

呼び出し元にはたまたまそこにあった値が
テキトーに格納されてしまうが、アクセスしなければ良い。

呼び出し元から呼び出し先に、
変数の内容が
移動した
ことになる
C++11 では『破壊してもいいオブジェクト』は右参照で
表現できるようになりました。

C++11 で文字列のコピーをすることなく先の Object::value を設定するコードは次のようになります。
文字列のコピーをせずに文字列を設定する。 C++11 版。(暫定)

// value を設定する。
void
Object::SetValue(
string&& v
){

// move 代入
this->value =
move( v )
;
}
void
PiyoPiyo() {
Object obj;
string x = ....
// どこかにある文字列


// obj.value を設定、x は破壊される、以後アクセスしない。
obj.SetValue(
std::move( x )
);
}
与えられた変数を rvalue として返す関数。
(実際はキャストしてるだけ。)

『俺はもう触らないから呼び出し先で壊していいぞ』
という意味。
std::move
v を破壊して value の内容を作る。
(swap とほぼおなじ)
右参照自身は rvalue ではない。
変数から rvalue を作るには
std::move を使う。
実を言うと
// value を設定する。
void
Object::SetValue(
string&& v
){

// move 代入
this->value = move( v );
}
こういう関数はあんまりつくりません。
Move Constructor
があるためです。
Move Constructor
引数をひとつ持つコンストラクタは
値の暗黙の型変換に使用されます。
右参照は組み込みの型の一つなので
これが同じように適応されて、
右参照を受けるコンストラクタは
一時オブジェクトからの変換に使用されます。

Moveコンストラクタの作り方
自分のクラスの右参照を受けるコンストラクタを作る。

引数で受けたオブジェクトの内容をぶんどる。

引数で受けたオブジェクトのデストラクタが呼ばれて困らない程度にテキトーな値を詰めとく
// オレString
class
String {

const char
* value;

public
:
// ムーブコンストラクタ

String(
String&& other
) {
// オマエのものはオレのもの

this->value = other.value;

// value はもうオレのものだから
// オマエは null でももってろ

other.value =
nullptr
;
}
};
・・・と、Move 代入
似たようなのに Move 代入ってのもありますが、まぁ同様に。
おんなじ感じで右参照受ける operator= つくってあげてください。
デストラクタ、Moveコンストラクタ、Move代入が適切に作成されたクラスを
Moveのセマンティクスを持つ型
と表現します。

C++03で『値のセマンティクスを持つ』と表現された型は、C++11では
『コピーのセマンティクスを持つ』と表現されます。
Moveのセマンティクスを持つ型は
一時オブジェクトから非常に低いコストで構築することができます。
標準ライブラリの多くの型はC++11でMoveのセマンティクスを持つように
作りなおされています。

そのため、それらの型の一時オブジェクトを受けるとき、
右参照で受ける必要はありません。

値で受けてしまえばよい
のです。
ということで・・・
これはこうなります。
文字列のコピーをせずに文字列を設定する。 C++11 版。

// value を設定する。
void
Object::SetValue(
string v
){

// move 代入
this->value =
move( v )
;
}
void
PiyoPiyo() {
Object obj;
string x = ....
// どこかにある文字列


// obj.value を設定、x は破壊される、以後アクセスしない。
obj.SetValue( std::
move( x )
);
}
値で受ける
(右参照で受ける必要はない)
このシグネチャ
void
Object::SetValue(
string v
);
これは C++03 では避けるべきシグネチャで、
(不要なコピーが行われることがある)
const 参照を使うべきでした。

C++11 では状況によって問題ないものとなります。
むしろ、状況によっては const 参照よりもこちらを選ぶべきです。

この点において、

C++03 と同じ考え方では
C++11 でまともなプログラミングはできないのです。
その他の C++11 の要素は知らなくとも最悪『ダサい』と思われるだけです
C++の新しい常識
C++03
では
オブジェクトを値で渡すこと
オブジェクトを値で返すこと
これらはほぼ
コピーコンストラクタを呼び出すこと
と考えて
問題ありませんでした。


コピーは不要であるなら避けるべきであるため、
避けられるのにオブジェクトを値でやりとりすることは、
と考えられ、修正されるべきものでした。
pessimization
不適化
pessimization
pessimization
は最適化(optimazation)の対義語。
C++ Coding Standards
でハーブサッターが提唱したもの。
特に意味なくパフォーマンスを落とす要因を作ること。
浜田 真理さんの翻訳では『最不適化』
浜田さんの翻訳はそつがなく好きですが、この訳はどうかと思う。
時期尚早なoptimizeはするべきではないが、時期尚早なpessimize を避けることは常に行わなければいけない。
不適化
C++11
では
オブジェクトを値で渡すこと
オブジェクトを値で返すこと
これらは
状況によって copy が行われたり move が行われたり
します。

そのため大きなオブジェクトを値でやりとりするからといって
それが、不適化であるとは限りません。

むしろ move できるものを const 参照で受け、
copy を強要することのほうが C++11では不適化
になります。
『とにかく const 参照にしとけ』で良かったわけです。
値渡しの意味が変わっちまっているのです。
簡単にいってしまうと・・・・
なかで move するなら値で受けとけ。
引数
そのオブジェクトがmoveのセマンティクスを持っていて、
その場で作るんなら値で返しとけ。
戻り値
そのオブジェクトがmoveのセマンティクスを持っていて、
(C++03の常識) const 参照による引数渡しは、
・オブジェクトがその型であればそのまま参照で受ける
・一時オブジェクトであればコピーコンストラクタを通して受ける
という意味でした。
これは
呼び出し先が受け取った内容を保持しないのであれば
最適な解でした。
多くの、いわゆる setter やコンストラクタ引数では、呼び出し先は引数の内容をそのまま保持しようとします。
ここで発生するコピーは C++03 的には
しかたがないことでした。
// 文字列からのコンストラクタ
Object::Object(
const
string& v)
: value( v )
{ }
ここではコピーしないけど
ここではコピーしてしまう
C++11 ではこのコピーはしかたなくないのです。
できることなら move したい。
// 文字列からのコンストラクタ
Object::Object( string v)
: value( std::move( v ) )
{ }
ここは呼び出し元で move されたり
コピーされたり。
つまり、値で受けたい。
ここは自前で move する
あれ?
C++11 のコード引数でコピーされちゃわない?
されます。
けど、タイミングが違うだけ。
そのコピーが必要なときはC++03のコードでは中でコピーが起こってます。
でも、それは中でそのまま move したいときだけの話。
// 文字列からのコンストラクタ
Object::Object( string v)
: value( "" )
{
cout << v << endl;
}
move できない場合は呼び出し元でコピーしちゃった場合に
そのコピーが無駄になります。
なんでもかんでも値で受ければいいわけじゃないのです。
ここは呼び出し元で move されたり
コピーされたり。
自前で move ・・・しないのかよ!
引数でコピーされちゃった時無駄じゃん?
全部 move でいけるならば move で行くべき
(つまり値を受けたほうが良い)

move しないのであれば、
従来通り const 参照を受けたほうが良い。
引数の渡し方
引数の渡し方
C++03 では戻り値の一時オブジェクトのコピーを避けるため
返却先オブジェクトを引数で受けてそこに格納する
ということが広く行われていました。
大きなオブジェクトの返し方
// 文字列を指定文字で分割し、ret に返す
void
split(
const
string& source,
char
ch, vector<string>& ret);
このような戻り値の一時オブジェクトのコピーを避けるための努力はほとんど意味がなくなりました。
返そうとしている値が move のセマンティクスを持っているならば、そのまま値で返してしまえばよいのです。
// 文字列を指定文字で分割し、ret に返す
vector<string> split(
const
string& source,
char
ch);
当然のことですが、どこかに保持された変数を参照させたいという意味では、素直に参照を使うべきです。

何でもかんでも値を返せばいいわけじゃないのです。
// なにか大きなオブジェクト
class
BigData {
vector<string> lines;
public:
// これは勘弁して
vector<string> getLines() {
return
lines; }
};
ここでは
仮に自分のプロジェクトで C++11 への移行が行われるとしたら
プロジェクトのメンバにどんなことを叩きこむか
ということをお話したいと思います。
まとめ
C++11 の要素は別に知らなくてもプログラミングはできる。
ただし
イケメンに限る
rvalue reference をのぞく。
値型は move のセマンティクスを持つようにする。
コピーを避けるための常識は過去の遺産。
中でmoveするなら値を受けろ、中で作ったものは値で返せ。
ご清聴ありがとうございました。
Full transcript