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

右辺値参照

C++ Sample

C++11では、右辺に置かれる定数リテラルや文字列リテラル、オブジェクトのコンストラクタや関数などは、右辺値と呼ぶ・・・。
一方、左辺に置かれる変数やオブジェクト、ポインタなどは左辺値と呼ぶ・・・。

そして、C++03以前では単に「参照」と呼ばれていたものは「左辺値参照」というふうに呼ばれるようになった・・・。
左辺値参照が、変数などの恒久的なオブジェクトの参照を指すのに対し、右辺値参照は定数などの一時的なオブジェクトの参照を指す・・・。

といってもよくわからないとおもうので、今回は右辺値参照宣言子で右辺値を参照する変数を宣言してみる・・・。

test.hで、

クラスclass_testのコンストラクタ、メンバ関数member_function、そしてクラスとは別にグローバル関数のglobal_function・・・。

test.cppで、

class_testのコンストラクタは"constructor"を出力するだけ・・・。
member_functionは10を返し、global_functionは100を返すだけ・・・。
特に意味はない・・・。

左辺値参照(これまでの参照)は、

<型> &<参照名> = <参照するオブジェクト>

なのに対し、右辺値参照は、

<型> &&<参照名> = <参照する左辺値>

と書く・・・。

で、main.cppは、

// ヘッダのインクルード
// 既定のヘッダ
#include <iostream> // C++標準入出力
// 独自のヘッダ
#include "test.h" // クラスclass_test

// main関数の定義
int main(){

  // 変数・オブジェクトの宣言・初期化
  int num = 1; // int型変数numを1に初期化.
  class_test test; // class_test型オブジェクトtest.

  // 左辺値参照.
  int& l_num = num; // 変数numは左辺値.
  int&& r_num = num; // 右辺値ではない.
  class_test& l_test = test; // オブジェクトtestは左辺値.
  class_test&& r_test = test; // 右辺値ではない.

  // 右辺値参照.
  int&& r_1 = 1; // 定数リテラルは右辺値.
  int& l_1 = 1; // 左辺値ではない.
  class_test&& r_test = class_test(); // コンストラクタclass_test()は右辺値.
  class_test& l_test2 = class_test(); // 左辺値ではない.
  int&& r_global = global_function(); // 関数global_function()は右辺値.
  int& l_global = global_function(); // 左辺値ではない.

  // 値の出力.
  std::cout << "l_num = " << l_num << std::endl; // l_numを出力.
  std::cout << "r_1 = " << r_1 << std::endl; // r_1を出力.
  std::cout << "l_test.member_function = " << l_test.member_function() << std::endl; // l_test.member_function()を出力.
  std::cout << "r_test.member_function = " << r_test.member_function() << std::endl; // r_test.member_function()を出力.
  std::cout << "r_global = " << r_global << std::endl; // r_globalを出力.

  // プログラムの終了
  return 0;

}

まずは、こんな感じで・・・。
変数、オブジェクト、定数、コンストラクタ、関数、のそれぞれの右辺値参照と左辺値参照を取れるか試してみる・・・。
でこれでコンパイルすると、

$ g++ -o main main.cpp test.cpp -std=c++11
main.cpp: 関数 ‘int main()’ 内:
main.cpp:16:17: エラー: cannot bind ‘int’ lvalue to ‘int&&’
   int&& r_num = num; // 右辺値ではない.
                 ^
main.cpp:18:25: エラー: cannot bind ‘class_test’ lvalue to ‘class_test&&’
   class_test&& r_test = test; // 右辺値ではない.
                         ^
main.cpp:22:14: エラー: invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’
   int& l_1 = 1; // 左辺値ではない.
              ^
main.cpp:23:16: エラー: redeclaration of ‘class_test&& r_test’
   class_test&& r_test = class_test(); // コンストラクタclass_test()は右辺値.
                ^
main.cpp:18:16: エラー: ‘class_test&& r_test’ previously declared here
   class_test&& r_test = test; // 右辺値ではない.
                ^
main.cpp:24:36: エラー: invalid initialization of non-const reference of type ‘class_test&’ from an rvalue of type ‘class_test’
   class_test& l_test2 = class_test(); // 左辺値ではない.
                                    ^
main.cpp:26:35: エラー: invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’
   int& l_global = global_function(); // 左辺値ではない.
                                   ^
$

こうなる・・・。
まずnumは変数なので右辺値参照は取れない・・・。
testもオブジェクトなので右辺値参照はエラー・・・。
1は定数なので左辺値参照がそもそもエラー・・・。
class_test()はコンストラクタで右辺値なのでr_test自体は通るが、ここでは再定義エラーになっている・・・。
testはオブジェクトなので左辺値・・・。ここも再定義エラー・・・。
l_test2は左辺値参照で取ろうとしてるが、class_test()はコンストラクタで右辺値なのでエラー・・・。
l_globalも左辺値参照で取ろうとしてるが、global_function()は関数で右辺値なのでエラー・・・。

なので、

とコメントすると、

$ g++ -o main main.cpp test.cpp -std=c++11
$ ./main
constructor
constructor
l_num = 1
r_1 = 1
l_test.member_function = 10
r_test.member_function = 10
r_global = 100
$

コンパイルが通って、実行できる・・・。
結果は値を参照できたってだけ・・・。

こんな感じで、左辺値は左辺値参照しか取れないし、右辺値は右辺値参照しか取れない・・・。
ちなみに右辺値を参照している参照変数そのものは左辺値なので注意・・・。(変数だからね・・・。)

Sample/cpp/rvalue_reference/rvalue_reference/src/rvalue_reference at master · bg1bgst333/Sample · GitHub

代入演算子のオーバーロード

C++ Sample

コピーコンストラクタの項で、「オブジェクトの代入は、コピーではあるがコピーコンストラクタの対象外で、この場合コピー代入演算子オーバーロードが必要」ということを書いた・・・。
このオブジェクトの代入の場合も、アドレスのコピーをしないようにコピー処理を書かなければならない・・・。
今回はオブジェクトを代入した時の処理を、代入演算子オーバーロードで定義する・・・。

profile.hで、

これが代入演算子オーバーロード・・・。
引数のobjが左辺のオブジェクト参照でこれを元に新しいメモリを確保してコピーする処理を書かないといけない・・・。

profile.cppは、

実態としては、objの各メンバをset_profileに渡して呼んでいるだけ・・・。
でset_profileで、

すでにセットされている場合はいったん解放して、また確保してコピーという感じ・・・。
それぞれ、どの関数が呼ばれたか一応出力するようにしている・・・。

main.cppは、

まず、obj1に"Taro"、20、"Tokyo"をセット・・・。
obj1のメンバをいったん出力・・・。
次に、obj2をobj1で初期化・・・。
この場合はコピーコンストラクタでコピーされる・・・。
obj2を出力・・・。
続いてobj3は、"Jiro"、18、"Osaka"という全く違うデータをセット・・・。
obj3を出力・・・。
最後にobj3にobj2を代入する・・・。
この時、ようやく代入演算子オーバーロードが呼ばれる・・・。
これでobj3を出力・・・。

すると、

$ g++ -o main main.cpp profile.cpp
$ ./main
constructor
set_profile
get_name
obj1.get_name() = Taro
get_age
obj1.get_age() = 20
get_address
obj1.get_address() = Tokyo
copy_constructor
get_name
obj2.get_name() = Taro
get_age
obj2.get_age() = 20
get_address
obj2.get_address() = Tokyo
constructor
set_profile
get_name
obj3.get_name() = Jiro
get_age
obj3.get_age() = 18
get_address
obj3.get_address() = Osaka
set_profile
assignment_operator_overload
get_name
obj3.get_name() = Taro
get_age
obj3.get_age() = 20
get_address
obj3.get_address() = Tokyo
before delete [] name_ = 0x1ba40b0
before delete [] address_ = 0x1ba4090
destructor
before delete [] name_ = 0x1ba4050
before delete [] address_ = 0x1ba4070
destructor
before delete [] name_ = 0x1ba4010
before delete [] address_ = 0x1ba4030
destructor
$

obj3にJiro、18、Osakaをセットした後に、obj2を代入するところでassignment_operator_overloadが呼ばれている・・・。
その後、obj3は見事にobj2と同じTaro、20、Tokyoとなり、それぞれのデストラクタでもきちんとメモリは解放されている・・・。

Sample/cpp/assignment_operator_overload/assignment_operator_overload/src/assignment_operator_overload at master · bg1bgst333/Sample · GitHub

ObjeqtNote #23 エディットボックスをウィンドウ一杯に広げる

WindowsAPI Application B.G-STATION

こっちもエディットボックスを一杯に広げて使うので広げる・・・。

f:id:BG1:20170221233510p:plain

こんな感じになる・・・。
スクロールもできる・・・。

・エディットボックスをウィンドウ一杯に広げる · bg1bgst333/ObjeqtNote@e6af71f · GitHub

Aoi #22 エディットボックスをウィンドウ一杯に広げる

WindowsAPI Application B.G-STATION

そろそろ、テキストエディタなので、エディットボックスをウィンドウ一杯に広げたい・・・。

まず、Window.hで、

MoveWindowとOnSizeを追加・・・。
MoveWindowは位置とサイズを指定する感じ・・・。

Window.cppで、

MoveWindowは引数をメンバにセットしつつ、APIのMoveWindowで位置サイズ変更・・・。

ウィンドウサイズの変更はWM_SIZEなので、その時の処理はOnSizeに任せる・・・。
引数は型変換がちょっと必要・・・。

で、Window.cppのOnSizeは特に何もしない・・・。

CustomControl.cppはメッセージ処理が別なので、ここもOnSizeに任せる・・・。
このOnSizeはWindow.cppのOnSize・・・。

MainWindow.hにもOnSize・・・。

MainWindow.cppのm_pEditBox->Createで、WS_BORDERを外したり、ES_WANTRETURNや、縦横両方のスクロールバーのスタイルなどを追加・・・。

OnSizeでm_pEditBox->MoveWindowでウィンドウサイズ変更・・・。
cx, cyにウィンドウのクライアント領域の変更後のサイズが格納されるので、m_pEditBoxもそのサイズに合わせる・・・。

f:id:BG1:20170221230921p:plain

こんな感じで画面いっぱいにテキストを書ける・・・。

・エディットボックスをウィンドウ一杯に広げる · bg1bgst333/Aoi@dc18662 · GitHub

ObjeqtNote #22 テキストファイル(*.txt)のフィルタを追加

WindowsAPI Application B.G-STATION

こちらもtxtファイルのフィルタを追加・・・。

f:id:BG1:20170221184804p:plain

txtを選択できる・・・。

・テキストファイル(*.txt)のフィルタを追加 · bg1bgst333/ObjeqtNote@ed71c91 · GitHub

Aoi #21 テキストファイル(*.txt)のフィルタを追加

WindowsAPI Application B.G-STATION

ここまでbinファイルしか保存できなかったので、txtファイルのフィルタも追加する・・・。

TextFile.hに、

txtファイルのフィルタを追加・・・。

MainMenuBar.cppに、

と、

というようにフィルタ文字列の作成にtxtの処理も追加・・・。

f:id:BG1:20170221182231p:plain

開いて、

f:id:BG1:20170221182244p:plain

テキストファイル(*.txt)というフィルタがあり、txtファイルを選択できる・・・。

f:id:BG1:20170221182528p:plain

もちろん読み込める・・・。

f:id:BG1:20170221182557p:plain

"かきくけこ"を追加し、名前を付けて保存・・・。

f:id:BG1:20170221182622p:plain

ここでもtxtは選択可能・・・。
test1.txtを選択・・・。

f:id:BG1:20170221182655p:plain

上書き確認・・・。
"はい"なら、

f:id:BG1:20170221182822p:plain

保存されてる・・・。

・テキストファイル(*.txt)のフィルタを追加 · bg1bgst333/Aoi@dc600cf · GitHub

ObjeqtNote #21 ファイル読み込み&文字コード変換&エディットボックスにテキストをセット

WindowsAPI Application B.G-STATION

こちらも、読み込みからテキストセットまで一気に・・・。

f:id:BG1:20170220175315p:plain

最初はこうで、

f:id:BG1:20170220175341p:plain

選択すると、

f:id:BG1:20170220175353p:plain

内容を読み込んでセット・・・。

・ファイル読み込み · bg1bgst333/ObjeqtNote@9f82827 · GitHub