﻿講義メモ
・「6.メモリの管理：6.5 スマートポインタ：リソースの所有権」の続きから
https://rinatz.github.io/cpp-book/ch06-05-smart-pointers/

6.メモリの管理：6.5 スマートポインタ：リソースの所有権【再掲載】

・ポインタはコピー可能なため、ポインタが指す先のリソースを複数のオブジェクトから参照することが可能
・動的確保したリソースを扱う場合、 誤って delete を忘れたり、同じリソースを複数回 delete したりすることを防ぐために、 どの変数がリソースの所有権（リソースを参照する権利と解放する権利）を持つのかをプログラマが細心の注意を払ってコードを書く必要がある

提出フォロー：演習：リソースの所有権 project1/p0604501.cpp

・リソースの所有権のサンプルソースを動作可能にしよう
・「a と b のどちらを delete するべきか？」の下で両方をdeleteして、どうなるか確認しよう
⇒ どちらをdeleteしても良いが、両方を消そうとすると、delete済なので異常終了する

作成例

//演習：リソースの所有権 project1/p060501.cpp
#include <iostream>
int main() {
    int* a = new int(100); //動的に領域を確保
    int* b = a; // b からも a と同じリソースを参照できるようにする。
    // a と b のどちらを delete するべきか？
    delete a;
    std::cout << "delete a is done" << std::endl;
    delete b; //対象が存在しないので異常終了する
    std::cout << "delete b is done" << std::endl;
    return 0;
}

6.メモリの管理：6.5 スマートポインタ：std::shared_ptr

・動的確保したリソースの所有権を共有することができるスマートポインタ
・スマートポインタは <memory> ヘッダにて提供されている
・内部で所有権を持つオブジェクトの一覧を管理し、所有者がいなくなった時に自動的に delete する仕組みを提供する
・std::shared_ptr オブジェクトを生成するには、 std::make_shared を利用する
・デストラクタはオブジェクトの生存期間が尽きる（消える）とシステムが判断したときに自動的に呼び出す関数
・デストラクタはクラスの中で記述するものだが（副テキストp.471）、スマートポインタの利用により、動的に確保された領域に対しても delete が動作するようになっている

演習：std::shared_ptr project1/p060502.cpp

・std::shared_ptrのサンプルソースを試してみよう
・xとyの明示的な消去（delete）ができるかどうか確認しよう

作成例

//演習：std::shared_ptr project1/p060502.cpp
#include <iostream>
#include <memory>
int main() {
    std::shared_ptr<int> x = std::make_shared<int>(100); // int* x = new int(100); の代わり
    // 所有者は1人。
    {
        std::shared_ptr<int> y = x; // 通常のポインタ同様、コピーすることで所有権が共有される
        // 所有者が2人に増える。
        std::cout << "*y = " << *y << std::endl;
        // delete y; //シェアードポインタのdeleteは不可
    } // y が破棄されて所有者が1人になる。

    std::cout << "*x = " << *x << std::endl;
    // delete x; //シェアードポインタのdeleteは不可
    return 0;
} // 所有者が0人になるので、 x のデストラクタで自動的に delete が行われる。

6.メモリの管理：6.5 スマートポインタ：std::unique_ptr

・std::shared_ptr と違い、コピーが出来ないのが std::unique_ptr。
・よって、そのため、確保したリソースの所有者が常に1人になるようにしたい場合に用いる。

演習：std::unique_ptr project1/p060503.cpp

・std::unique_ptrのサンプルソースを試してみよう
・「// std::unique_ptr<int> y = x;」のコメントアウトを戻して、どういうエラーになるか確認しよう
　⇒ コンパイルエラーになるが、VC++では「C++関数は参照できません -- これは削除された関数です」というエラーが表示され、誤解を招くので注意。

作成例

//演習：std::unique_ptr project1/p060503.cpp
#include <iostream>
#include <memory>
int main() {
    std::unique_ptr<int> x(new int(100)); //コピーできないスマートポインタで生成される
    std::unique_ptr<int> y = x; // コピー出来ないのでコンパイルエラーになる
    std::cout << *x << std::endl;
    return 0;
} // x が所有しているリソースが解放される。

6.メモリの管理：6.5 スマートポインタ：std::unique_ptr（続き）

・所有権の共有はできないが、std::move を使うことで所有権の移動は出来る
・このことで std::shared_ptr よりも軽快に動作する

演習：std::unique_ptr その２ project1/p060504.cpp

・std::unique_ptrの２つめのサンプルソースを試してみよう
・「所有権を移動したため、x は何も所有していない」ことを確認する処理を追記しよう

作成例

//演習：std::unique_ptr その２ project1/p060504.cpp
#include <iostream>
#include <memory>
int main() {
    std::unique_ptr<int> x(new int(100));
    std::unique_ptr<int> y(std::move(x)); // ムーブは出来るため、所有権の移動は可能。
    std::cout << "*y = " << *y << std::endl;
    // 所有権を移動したため、x は何も所有していない。
    std::cout << "*x = " << *x << std::endl; //よってここで異常終了する（コンパイルエラーにはならない）
    return 0;
} // y が所有しているリソースが解放される。

※ std::auto_ptr、std::weak_ptrは割愛

7. クラス：7.1 データメンバ
　https://rinatz.github.io/cpp-book/ch07-01-data-members/
	
・クラス：構造体の上位概念で、変数と関数を集約した型をつくるための仕組み
・なお、C/C++の構造体は変数を集約した型をつくるための仕組み
　※ C#の構造体は軽量のクラスなので、C/C++の構造体とは意味が異なる
・メンバ：クラスの構成要素のことで、主に変数と関数
・データメンバ：クラスが持つ変数で、メンバ変数ともいう
・クラスが持つメンバは基本的にクラス内でのみ有効で、外部からアクセスできないが「public:」以降のメンバはアクセス可能
・クラスの基本的な定義書式： class クラス名 { メンバの定義; … }; //末尾にセミコロンが必要
・外部からアクセスできるデータメンバを持つクラスの書式例：
class クラス名 { 
　public:
　データメンバの型 データメンバ名;
　：
}
・データメンバを用いる側では、クラスを型とする変数を定義し、データメンバ名の前に「変数名.」を前置することで、通常の変数と同様に利用できる
　書式例： クラス名 変数名; 変数名.データメンバ名 = 値;
・なお、C++ではクラスを型とする変数を定義した時点で、クラスの実体であるインスタンスが自動生成される（この場合、C#の様にnewで生成する必要はない）

演習：データメンバ その１ project1/p070101.cpp

・データメンバのサンプルソース（「データメンバを参照するには」まで）を動作可能にしよう
・データメンバ値の表示を追加すること
・クラス定義はmain関数の外に置くと良い

作成例

//演習：データメンバ その１ project1/p070101.cpp
#include <iostream>
class Rectangle { //四角形クラスの定義
public: //これ以降は公開メンバ
    int height_; //データメンバ（高さ）
    int width_; //データメンバ（幅）
};
int main() {
    Rectangle r; //クラスを型とする変数の宣言（インスタンスが生成される）
    r.height_ = 10; //データメンバ（高さ）に代入
    r.width_ = 20; //データメンバ（幅）に代入
    std::cout << " height = " << r.height_ << std::endl; //データメンバ（高さ）の値を表示
    std::cout << " width = " << r.width_ << std::endl; //データメンバ（幅）の値を表示
    return 0;
}

7. クラス：7.1 データメンバ（つづき）

・クラスには大量のデータメンバや要素数の大きな配列を記述できる
・すると、インスタンスも大容量になるので、変数ではなくポインタで扱うと効率的
・クラスのオブジェクト（インスタンス）をポインタで扱うには、変数の場合と同様に「*」と「&」を用いる
　書式例： クラス名* ポインタ名 = &変数名;
・データメンバをポインタ経由で参照するには「変数名.」を「(*ポインタ名).」とする
　※メンバ演算子「.」がポインタ解決演算子「*」より優先度が高いのでカッコが必要
・この記法は見づらく、ミスの元なので、アロー演算子「->」を用いて「ポインタ名->メンバ名」とすれば良い

演習：データメンバ その２ project1/p070102.cpp

・データメンバのサンプルソース（「(*r). の代わりに r->」）を動作可能にしよう
・データメンバ値の表示を追加すること

作成例

//演習：データメンバ その２ project1/p070102.cpp
#include <iostream>
class Rectangle { //四角形クラスの定義
public: //これ以降は公開メンバ
    int height_; //データメンバ（高さ）
    int width_; //データメンバ（幅）
};
int main() {
    Rectangle rectangle; //クラスを型とする変数の宣言（インスタンスが生成される）
    Rectangle* r = &rectangle; //インスタンスを指すポインタを定義
    r->height_ = 10; //データメンバ（高さ）にポインタ経由で代入
    r->width_ = 20; //データメンバ（幅）にポインタ経由で代入
    std::cout << " height = " << r->height_ << std::endl; //ポインタ経由で値を表示
    std::cout << " width = " << r->width_ << std::endl; //ポインタ経由で値を表示
    return 0;
}

7. クラス：7.2 メンバ関数

・クラスには（構造体とは異なり）メンバとして関数を定義できる
・これにより、データとデータを扱う処理をまとめて扱えること（オブジェクト指向・部品の基礎）が可能
・メンバ関数の書式は通常の関数と同様だが、定義の上下関係による呼び出しや利用の可否はない
・よって、あるメンバ関数の定義位置より下で定義しているデータメンバや他のメンバ関数を利用・呼び出し可能
・メンバ関数を用いる側では、クラスを型とする変数を定義し、メンバ関数名の前に「変数名.」を前置することで、通常の関数と同様に利用できる

演習：メンバ関数 project1/p070201.cpp

・メンバ関数のサンプルソースを動作可能にしよう
・メンバ関数Area()の戻り値を表示する処理を追記すること

作成例

//演習：メンバ関数 project1/p070201.cpp
#include <iostream>
class Rectangle { //四角形クラスの定義
public: //これ以降は公開メンバ
    int Area() { //メンバ関数「面積」
        return height_ * width_; //下方で定義しているデータメンバを利用
    }
    int height_; //データメンバ
    int width_; //データメンバ
};
int main() {
    Rectangle r; //クラスを型とする変数の宣言（インスタンスが生成される）
    r.height_ = 10; //データメンバ（高さ）に代入
    r.width_ = 20; //データメンバ（幅）に代入
    std::cout << "面積 = " << r.Area() << std::endl; //メンバ関数を呼んで戻り値を表示
}

7. クラス：7.2 メンバ関数：クラス宣言とは別に定義

・クラス宣言の中に関数定義を記述するとクラス定義の内容が長文化して見通しが悪くなる
・そこで、クラス定義の中にはメンバ関数のプロトタイプ宣言を置き、メンバ関数の内容は別に定義することが可能
・別に定義する場合は、メンバ関数名の前に「クラス名::」を前置すること

演習：メンバ関数：クラス宣言とは別に定義 project1/p070202.cpp

・「クラス宣言とは別に定義」のサンプルソースを動作可能にしよう
・メンバ関数Area()の戻り値を表示する処理を追記すること
・また、別に定義するRectangle::Area()の記述場所が自由であることも確認しよう
　⇒ クラス定義の後であれば、main()関数の前でも後でも良い

作成例

//演習：メンバ関数：クラス宣言とは別に定義 project1/p070202.cpp
#include <iostream>
class Rectangle { //四角形クラスの定義
public: //これ以降は公開メンバ
    int Area(); //メンバ関数「面積」のプロトタイプ宣言
    int height_; //データメンバ
    int width_; //データメンバ
};
int Rectangle::Area() { //メンバ関数「面積」の定義をクラスの外で行う
    return height_ * width_; //下方で定義しているデータメンバを利用
}
int main() {
    Rectangle r; //クラスを型とする変数の宣言（インスタンスが生成される）
    r.height_ = 10; //データメンバ（高さ）に代入
    r.width_ = 20; //データメンバ（幅）に代入
    std::cout << "面積 = " << r.Area() << std::endl; //メンバ関数を呼んで戻り値を表示
}

7. クラス：7.2 メンバ関数：暗黙的な inline 指定

・inline 指定については「4.5 inline関数」を参照
　https://rinatz.github.io/cpp-book/ch04-05-inline-functions/
・クラス宣言の中でメンバ関数を定義した場合、暗黙的にinline指定が行われる
・これにより、クラスの定義をヘッダ内で行い、その中でメンバ関数を定義しても良い。

演習：メンバ関数：暗黙的な inline 指定 project070203/rectangle.h,something.h,something.cpp,main.cpp

・「暗黙的な inline 指定」前半のサンプルソースの動作を確認しよう
・４ソースで構成しているので、専用のプロジェクトを作成すると良い

作成例

//演習：メンバ関数：暗黙的な inline 指定 project070203/rectangle.h
#ifndef RECTANGLE_H_
#define RECTANGLE_H_
class Rectangle { //四角形クラスの定義
public: //これ以降は公開メンバ
    int Area() { //メンバ関数「面積」
        return height_ * width_; //下方で定義しているデータメンバを利用
    }
    int height_; //データメンバ
    int width_; //データメンバ
};
#endif  // RECTANGLE_H_

//演習：メンバ関数：暗黙的な inline 指定 project070203/something.h
#ifndef SOMETHING_H_
#define SOMETHING_H_
void Something(); //通常関数のプロトタイプ宣言
#endif  // SOMETHING_H_

//演習：メンバ関数：暗黙的な inline 指定 project070203/something.cpp
#include "something.h" //自前のヘッダを利用
#include <iostream>
#include "rectangle.h" //クラス定義を含むヘッダを利用
void Something() { //ヘッダで宣言している関数の内容を記述
    Rectangle r; //クラスを型とする変数の宣言（インスタンスが生成される）
    r.height_ = 2; //データメンバ（高さ）に代入
    r.width_ = 3; //データメンバ（幅）に代入
    std::cout << r.Area() << std::endl; //メンバ関数を呼んで戻り値（面積）を表示
}

//演習：メンバ関数：暗黙的な inline 指定 project070203/main.cpp
#include <iostream>
#include "rectangle.h" //クラス定義を含むヘッダを利用
#include "something.h" //自前のヘッダを利用
int main() {
    Rectangle r; //クラスを型とする変数の宣言（インスタンスが生成される）
    r.height_ = 10; //データメンバ（高さ）に代入
    r.width_ = 20; //データメンバ（幅）に代入
    std::cout << r.Area() << std::endl; //メンバ関数を呼んで戻り値（面積）を表示
    Something(); //別ソース(something.cpp)で定義済の関数を呼ぶ
    return 0;
}

7. クラス：7.2 メンバ関数：暗黙的な inline 指定と「クラス宣言とは別に定義」

・クラス宣言とは別にメンバ関数を定義すると暗黙的な inline 指定はされなくなる
・よって、ヘッダファイル内のクラス宣言とは別にメンバ関数の定義を行うとリンク時にエラーとなる
・ヘッダファイル内でクラス宣言とは別にメンバ関数の定義を行うには、明示的に inline 指定する必要がある

提出：演習：メンバ関数：暗黙的な inline 指定 project070204/rectangle.h,something.h,something.cpp,main.cpp

・「暗黙的な inline 指定」後半のサンプルソースの動作を確認しよう
・inline指定をしていない場合にどうなるかを確認してから、inlineを追記しよう
・４ソースで構成しているので、専用のプロジェクトを作成すると良い
※ ４ソースを１つのZIPファイルに圧縮して「提出フォーム」から提出すること

次回予告：「7. クラス：7.2 メンバ関数：const メンバ関数」から

