3章 クラス

3-1 constメソッドについて

  • constメソッド
    • constインスタンスから呼び出される
      • 逆に、constインスタンスconstメソッド以外を呼び出すことはできない
    • メソッド内でメンバ変数を書き換えることはできない
    • mutableな変数のみ書き換えることができる
      • 使いどころが難しい
      • constインスタンスメンバ関数を書き換えるというのは直感に反する
      • メソッドの戻り値の計算に多大なコストが必要で、値をキャッシュする場合などに用いる
    • constの有無でメソッドのオーバーロードが可能
  • const変数
    • 書き換えができないメンバ変数
    • メンバ初期化子、もしくはメンバ初期化リストでのみ初期化することができる
  • https://wandbox.org/permlink/yIAc4PQpkqT6ji3p

3-2 コンストラクタ、デストラク

  • コンストラク
    • 基本的には初期化に関する実装のみを記載することが鉄則
    • コロン以降はメンバ初期化リストとよばれる
      • メンバ変数のコンストラクタを呼び出せる
  • デストラク
    • インスタンスが削除されるときに呼び出される
    • コンストラクタでメモリ確保、デストラクタでメモリの解放することをRAII (Resource Acquisition Is initializationとよぶ
    • OSが提供する特殊なリソースの返却などにも利用する

3-3 初期値を受け取るコンストラク

  • 何も定義しないとデフォルトコンストラクタがコンパイラにより作成される
    • デフォルトコンストラクタとは、引数のないコンストラクタのこと
    • 逆に、ひとつでも定義するとデフォルトコンストラクタは作成されない
  • コンストラクタ内で他のコンストラクタを呼び出す機能を移譲コンストラクとよぶ
    • 一般には、移譲先にコンストラクタの実装の大部分を任せる
    • 移譲元ではメンバ変数の初期化も行えない
  • 多くの場合はコンパイラが自動生成するコピーコンストラクタをそのまま使えば良い
  • 代入演算子と中括弧で暗黙のコンストラクタ呼び出しが行える
    • つまり、Hoge a={var1, var2};Hoge a(var1, var2);と同じ
    • しかし、Hoge a=42はパッと見てHoge a(42)と実装されているようには見えづらい
    • コンストラクタの頭にexplicitをつけることで、暗黙のコンストラクタ呼び出しを禁止することができる

3-4 デフォルトの初期値

  • クラス定義内でのメンバ変数の初期化の構文を非静的メンバ変数の初期化子(Non Static Data Member Initializer : NSDMI)と呼ぶ
    • NSDMIと呼ばれることが多い
  • 構文は下記
class A
{
    type1 var1 = defVal1;
    type2 var2 = {defVal2};
    type1 var3(defVal3);
    type1 var4{defVal4};
};
  • メンバー初期化子リストも与えられた場合はメンバー初期化リストが優先される

3-5 継承

  • ベースとなるクラスを基底クラス、基底クラスを継承したクラスを派生クラスとよぶ
    • 「クラスAから派生したクラスBが、基底クラスの機能を継承する」といいた言い回し
  • 構文は下記
class derived-class-name : access-specifier base-class-name
{
   ...
};
  • access-specifierを省略するとprivateになる
    • privateの場合、基底クラスのメンバに派生クラスからアクセスできなくなる
  • 基底クラスのメンバ関数の処理内容を派生クラスで変更することができる
    • 基底クラスのメンバ関数virtualを指定し、仮想関数にすることで、派生クラスで処理内容の変更を許可する
    • 派生クラスではoverride指定子を追加し、処理内容を書き換える
      • 派生クラスで何もoverrideしなければ、自動的に基底クラスの仮想関数を継承する
    • overrideする場合は戻り値の型、関数名、constの有無なども一致させる必要がある
    • 実は基底クラスでvirtualを指定しておけば、派生クラスのoverrideは省略できるが、わかりにくくなるので避ける
  • 派生クラスで基底クラスと同名のメンバ関数を作成すると名前の隠蔽(name hiding)が生じる
    • たとえ引数で区別したとしても基底クラスのメンバ関数を呼び出せなくなる
    • using宣言を用いることで、派生クラスでも基底クラスのメンバ関数を呼び出せるようになる
  • 派生クラスにオーバーライドを強制するメソッドを純粋仮想関数とよぶ
    • virtual method_name(args, ...) = 0;のように基底クラスで定義する
    • 派生クラスでオーバーライドしないとコンパイルエラーとなる
    • 純粋仮想関数を定義したクラスを抽象クラスとよび、インスタンスの生成ができない

3-6 オブジェクトポインター

  • 構造体と同じく、ポインタを経由してメンバにアクセスするときはアロー演算子->を使う
  • メンバ関数の仮引数とメンバ変数名が一致するとき、その変数名は仮引数として解釈される
    • メンバ変数にアクセスするときには、thisポインタを経由してアクセスする
    • thisポインタは書き換え不可
    • constメンバ関数の中ではthisポインタはconstポインタとなる

3-7 クラス、構造体、共用体の関係性

  • 構造体
    • クラスと構造体は、デフォルトのアクセス指定だけが異なる
    • クラスはprivate、構造体はpublic
    • 基底クラス(構造体)から派生するときのアクセス指定もクラスがprivateで構造体がpublic
    • 他に違いは無いが、Cからの名残で単にデータの箱をつくるときは構造体を用いることが多い
  • 共用体
    • メンバ変数はすべて同じアドレスに置かれるため、正しい意味を持つメンバ変数はどれか一つのみ
    • クラス/構造体/共用体から派生して共用体を作ることも、共用体から派生してクラス/構造体/共用体を作ることもできない
    • メンバ変数やコンストラクタなども定義できる
    • デフォルトのアクセス指定はpublic
    • メンバ関数内でthisもつかえる
  • 無名共用体
    • 共用体のインスタンスをつくるまでも無いが、複数の変数を同じアドレスに置きたい場合に利用する
    • 下の例では、クラスHogeインスタンスhogeについて、&(hoge.a) == &(hoge.b)となっている
    • 無名共用体はグローバル変数や関数内の変数としても使える
class Hoge
{
public:
    union
    {
        int a;
        float b;
    }
};

3-8 friend関数

  • メンバ関数でないにもかかわらず、privateなメンバにアクセスできるような関数を用意したい場合は、フレンド関数を使用する
    • フレンド関数はクラス宣言内でfriendをつけたものをプロトタイプ宣言する(フレンド宣言)
    • メンバ関数ではないので、引数としてクラスのポインタや参照を一つ以上受け取るのが一般的
    • 複数クラスの非公開メンバにアクセスしたい場合は、複数クラスでフレンド宣言する
    • 定義にはfriendは不要
  • フレンド宣言はクラスの外側では行えない

3-9 staticクラスメンバ

class A
{
public:
    static int stVal;
}
;

int A::stVal = 3;