インデクサ

インデクサは、プロパティを拡張したようなもので、オブジェクトのメンバに配列のような添字でアクセスできる・・・。
さらに、プロパティと同じく、getとsetでアクセス制限もできる・・・。
オブジェクトの中に、配列やリストのようなコレクションをメンバとしておいといて、それをオブジェクトの外からアクセスするようなときに使う・・・。

Class1.csを追加し、

Class1のコンストラクタで、配列の大きさsizeを引数に取る・・・。
そのsizeの分の配列intArrayを生成・・・。メンバにsizeをセットしておく・・・。

インデクサは、

<戻り値型> this[<添字型> <添字変数>]

で定義・・・。
上のgetでは、生成した配列の範囲外だったらエラーで-1を返し、範囲内ならintArray[index]の値を返す・・・。

下のsetでは、indexだけじゃなく、値が負の場合もエラー・・・。範囲内かつ値が0以上なら、値をintArray[index]に入れる・・・。

MainClass.csは、

Class1のオブジェクトc1を生成・・・。
コンストラクタに5を渡しているので、要素数5の配列が内部で生成されている・・・。

値の設定・・・。
添字が-3、添字が100、代入する値が-5の場合はエラー・・・。

c1のそれぞれの値を取得し、vに代入・・・。
添字が-2、15の場合はエラー・・・。
c1[4]は設定の時点でエラーなので値がセットされていない・・・。

c1とvの値を出力・・・。

invalid!(index < 0)
invalid!(index > size - 1)
invalid!(value < 0)
invalid!(index < 0)
invalid!(index > size - 1)
c1[0] = 1
c1[1] = 0
c1[2] = 8
c1[3] = 0
c1[4] = 0
v[0] = 1
v[1] = -1
v[2] = -1
v[3] = 8
v[4] = 0
続行するには何かキーを押してください . . .

設定でエラーが出ていないところはc1に0以外の値がセットされている・・・。
取得でエラーが出ている場合は-1がセットされている・・・。
v[4]はc1[4]の値がセットされずに取り出されたので0・・・。

Sample/cs/Indexer/Indexer/src/Indexer at master · bg1bgst333/Sample · GitHub

usingステートメント

これまで、Disposeによるリソース解放は、finallyで手動で行うような形になっていた・・・。
usingステートメントを使うと、IDisposableインターフェイス実装オブジェクトについては、finallyがなくても自動でリソース解放してくれる・・・。

CustomReader.csで、

ReadAllのtry-catch-finallyを外してみる・・・。

その代わり、MainClass.csでは、

usingの括弧の中に、CustomReaderの生成処理を書いている・・・。
そして、次のブロックの中で、ReadAllなどの読み込み処理をしている・・・。
usingブロックを抜けると、失敗しようが成功しようが、括弧の中で生成したオブジェクトのリソース解放処理Disposeは勝手に呼ばれる・・・。
ただし、usingとtry-catchは独立しているので、tryでの例外が起きそうな処理、発生した時のcatchでの例外処理は必要・・・。
なので、このようになっている・・・。

test1.txtだけの場合、

Error: ファイル 'C:\Project\Cloud\github.com\Sample\cs\usingStatement\usingState
ment\src\usingStatement\usingStatement\bin\Debug\test2.txt' が見つかりませんでし
た。
Dispose
sr1 closed!
続行するには何かキーを押してください . . .

finallyがなくてもDisposeが呼ばれている・・・。
test2.txtもあった場合は、

cr.text1 = ABCDE
cr.text2 = XYZ
Dispose
sr2 closed!
sr1 closed!
続行するには何かキーを押してください . . .

この場合もDisposeが呼ばれている・・・。

実は、StreamReaderなどのリソース生成解放処理がいるようなクラスはだいたいIDisposableが実装されている・・・。
なので、実際にはusingを使えば勝手にDisposeの解放処理が呼ばれるので、Disposeの処理を実装することはあまりないともいえる・・・。

Sample/cs/usingStatement/usingStatement/src/usingStatement at master · bg1bgst333/Sample · GitHub

IDisposable

IDisposableは、割り当てられたアンマネージドリソースを解放する機構を実装するためのインターフェイス・・・。

IDisposable インターフェイス (System)

リソースを明示的に解放しないといけないクラスにDisposeメソッドを実装し、この中で解放処理を実行するようにする・・・。

CustomReader.csを追加し、

ファイル名を2つ受け取るクラスのコンストラクタを定義・・・。

ReadAllで2つのファイルのストリームを連続して開く・・・。
開けたら、publicなtext1、text2に格納・・・。
例外ならエラーメッセージを出力・・・。
finallyでDisposeメソッドを呼び出すようにする・・・。

Disposeメソッドでは、"Dispose"と出力し、そのあとはそれぞれのストリームが開いていたら閉じている・・・。
finallyで呼ばれているので、成功でも失敗でもDisposeが呼ばれ、ストリームリソースが解放される・・・。

MainClass.csは、

"test1.txt"、"text2.txt"をCustomReaderで開く・・・。
読み込めたらtext1、text2を出力・・・。

test1.txtだけなら、

Error: ファイル 'C:\Project\Cloud\github.com\Sample\dotnet\IDisposable\IDisposab
le\src\IDisposable\IDisposable\bin\Release\test2,txt' が見つかりませんでした。
Dispose
sr1 closed!
続行するには何かキーを押してください . . .

となり、test2.txtもあれば、

Dispose
sr2 closed!
sr1 closed!
cr.text1 = ABCDE
cr.text2 = XYZ
続行するには何かキーを押してください . . .

となる・・・。
成功でも失敗でもDisposeを呼んでいる・・・。

Sample/dotnet/IDisposable/IDisposable/src/IDisposable at master · bg1bgst333/Sample · GitHub

finally

finallyは、tryブロックの中で例外が発生しても発生しなくても実行すべき処理を書く場所・・・。
今回は、2つのファイルを連続して読み込んで内容を出力・・・。
2つ目で例外が発生しても、1つ目のファイルを閉じる処理は実行するような形にする・・・。
MainClass.csで、

tryブロックで、StreamReaderで"test1.txt"を開いて、ストリームはsr1に格納・・・。
sr1.ReadToEndでテキストの内容が読み込まれてtext1に格納・・・。
Console.WriteLineでtext1の内容を出力・・・。
これを"test2.txt"でも同様に・・・。

catchは、ex.Messageを出力しているだけ・・・。

finallyは、"finally"といったん出力・・・。
そのあと、それぞれのストリームがnullでなければ、Closeで閉じて、"~ closed!"という感じで閉じたことを出力・・・。

f:id:BG1:20170226211920p:plain

test1.txtだけ存在する場合は、

text1 = ABCDE
Error: ファイル 'C:\Project\Cloud\github.com\Sample\cs\finally\finally\src\final
ly_\finally_\bin\Release\test2.txt' が見つかりませんでした。
finally
sr1 closed!
続行するには何かキーを押してください . . .

こうなる・・・。
finallyでsr2は失敗してnullなので閉じなくていいが、sr1は成功しているのでCloseで閉じている・・・。

f:id:BG1:20170226212339p:plain

test2.txtも存在する場合は、

text1 = ABCDE
text2 = XYZ
finally
sr2 closed!
sr1 closed!
続行するには何かキーを押してください . . .

こうなる・・・。
finallyでどちらもCloseで閉じている・・・。

こんな感じで成功しても失敗しても終了処理をする必要がある場合は、finallyに書く・・・。

Sample/cs/finally/finally/src/finally_ at master · bg1bgst333/Sample · GitHub

move(utility)

代入は、実質的にはオブジェクトのコピーである・・・。

obj = func(); // funcは右辺値なので一時オブジェクトをobjにコピーすることになる.

例えば、上のような場合にobjやfunc()の戻り値がint型ならまだしも、これが大きなクラスオブジェクトのコピーとなると、コピーコストも大きい・・・。
しかも、func()は右辺値であり、一時オブジェクトであるから、コピー後は不要・・・。いずれ破棄される存在・・・。コピーをすること自体無駄に思える・・・。

このように、一時オブジェクトの内容を別のオブジェクトに渡したい場合は、コピーではなく、所有権を移動する形(ムーヴ)にすればコピーコストも発生しない・・・。
C++11のutilityヘッダにあるstd::moveを使うと、あるオブジェクトから別のオブジェクトに内容をムーヴできる・・・。

右辺値参照・ムーブセマンティクス - cpprefjp C++日本語リファレンス
move (utility) - cpprefjp C++日本語リファレンス

std::moveは右辺値だけでなく、左辺値にも適用できる・・・。
今回は変数やオブジェクトなどの左辺値に対して、std::moveを使って別の変数やオブジェクトに所有権を移動する・・・。
するとどうなるか・・・。

int型のval、std::stringのstr、int型std::vectorのvec_iを宣言・・・。

val, str, vec_iに適当に値を代入したり、追加したりしてみる・・・。

いったん出力・・・。
vec_iは値だけでなくsizeも・・・。

std::moveにval, str, vec_iを渡して、戻り値をnew_val, new_str, new_vec_iとして、これらに所有権を移動する・・・。

すでに所有権のない無効なval, str, vec_iと新しい所有者new_val, new_str, new_vec_iの内容を出力・・・。

最後に、val, str, vec_iをいじってみる・・・。

実行すると、

$ g++ move.cpp -o move -std=c++11
$ ./move
val = 10
str = ABCDE
vec_i = 1, 2, 3
vec_i.size() = 3

val = 10
str =
vec_i.size() = 0
new_val = 10
new_str = ABCDE
new_vec_i = 1, 2, 3
new_vec_i.size() = 3

val = 20
new_val = 10
str = XYZ
new_str = ABCDE
vec_i[0] = 10
vec_i.size() = 1
new_vec_i = 1, 2, 3
new_vec_i.size() = 3
$

new_val, new_str, new_vec_iの方に内容が移動しているようにみえる・・・。
valの10は残っているように見えるけど、これはたまたままだ参照できているだけ・・・。
明け渡した以上、val, str, vec_iとnew_val, new_str, new_vec_iは独立している・・・。
所有権を明け渡したあとのオブジェクトに適当に値を入れたりしてみる・・・。
独立してるから、val, str, vec_iの値にnew_val, new_str, new_vec_iが引っ張られることは無い・・・。
いちおう、動くけど、実際は所有権を明け渡した後のオブジェクトの動作は未定義だか不明だか・・・。まあ、本来はやらないほうがいい・・・。

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

右辺値参照

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

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

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

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