Software_CPP2

┌──┐
│目次│
├──┘
│◆クラス class
│_◇クラスの定義 classdef
│_◇オブジェクトの生成 creatobj
│_◇引数としてオブジェクトを使う objasarg
│_◇クラスを指すポインタ classptr
│_◇thisポインタ this
│_◇クラスメンバの参照 refclassmem
│_◇publicとprivate public
│_◇コンストラクタ関数 constructor
│_◇コンストラクタ初期化子 constinit
│_◇コンストラクタ初期化の順番 initorder
│_◇コピーコンストラクタ copyconst
│_◇デストラクタ関数 destructor
│_◇演算子のオーバーロード oproverloading
│_◇挿入子のオーバーロード inlineoverloading
│_◇代入演算子の多重定義 assign
│_◇添字演算子の多重定義 array
│_◇挿入子、抽出子の多重定義 io
│_◇constメンバ関数 const
│_◇変換関数とユーザ定義変換 convfunc
│_◇静的メンバ staticmember
│_◇データメンバ datmem
│_◇入れ子クラス nestedclass
│_◇継承 inheritance
│_◇using宣言によるアクセス権の調整 using
│_◇汎化と特化 generalization
│_◇スライシング slice
│_◇隠蔽 hide
│_◇仮想関数 virtual
│_◇仮想デストラクタ virtualdestructor
│_◇多相的クラスとポリモーフィズム polymorphism
│_◇オブエジェクト指向プログラミングの三大要素 oop
│_◇多重継承 multipleinheritance
│_◇多重継承における基底クラス部分オブジェクトの初期化順序 mulinitorder
│_◇多重継承におけるクロスキャスト crosscast
│_◇多重継承例 multiinheritancesample
│_◇仮想派生 virtualderivation
│_◇抽象クラス abstractclass
│_◇抽象基底クラス abstractbaseclass
│_◇オブジェクトのクラスを調べる RTTI
│_◇フレンド friend
│_◇フレンド関数 friendfunc
│_◇フレンド関数による演算子のオーバーロード friendopr
│_◇クラステンプレート classtemplate
│_◇クラステンプレートの具現化とその条件 classtemplate2
│_◇メンバテンプレート membertemplate
│_◇非型のテンプレート仮引数 template3
│_◇typename typename


◆クラス

オブジェクトのデータとデータを操作するメソッドをメンバとしてまとめたもの
⇒C++のクラスは構造体型と共通点が多く、ユーザー定義型の一つ。


_◇クラスの定義

※クラス定義はキーワードclassから始まり}の後ろの;までつづく
⇒Javaとは異なり、クラス定義の末尾は必ず;が必要
⇒classに代えてstructを使って定義することもできる

クラス宣言 構文)

class クラス名 {
    アクセス指定子:
    変数の宣言;
    ...
    関数の宣言;
    ...
};

※メンバ member
データメンバ data member
│ ⇒データメンバはクラスのステートとも呼ばれる
メンバ関数 member function

⇒変数や関数は構造体と同様、メンバと呼ばれる
⇒変数(データメンバ)
⇒関数(メンバ関数)
⇒データメンバにモノの性質、状態を、メンバ関数にモノの機能をまとめる

※アクセス指定子 access specifier
│ private:
│ public:
│ protected:
⇒指定がないとprivateとなる
⇒structでは指定がなければpublic

例)

class class_name {
    int data_member;
    void show_member(int);
};

メンバ関数の定義 構文)

戻り値の型 クラス名::メンバ関数名(引数リスト)
{
│ …
}
⇒クラス定義の中でメンバ関数を定義すると内部結合のインライン関数となる
⇒必ずしもインライン展開されるとは限らない
⇒通常、メンバ関数の本体は、クラス宣言の外側で定義する。
⇒::(スコープ解決演算子 scope resolution operator)による指定が必須。

※オブジェクト(クラス型変数)の宣言
⇒クラス型の変数をオブジェクト(object)あるいはインスタンス(instance)と呼ぶ

例)
class_name object1, object2;

※クラスの宣言の中にメソッド定義そのものを入れてしまってもよい
│ inline function
⇒メンバ関数本体をクラス内で定義すると、インライン関数となる。
⇒C#のプロパティにあたるような関数はインラインとするとよい。

※クラスの中には関数プロトタイプを置き、クラスの外で関数を定義してもよい
→異なるクラスが同じ名前を持った関数を宣言できるので、クラスの外で関数を定義する場合には大域解決演算子が必要

例)
void employee::show_employee(void)
{
│ …

※クラス定義をヘッダとし、メンバ関数の定義を別のソースとして実現するのが一般的


_◇オブジェクトの生成

①変数として生成、破棄
例)
Car car1;
⇒変数のスコープにしたがって、生成、破棄される

②動的な生成、破棄
例)
Car* pCar;
pCar = new Car;

delete pCar;

※オブジェクトの初期化
⇒コピーコンストラクタ
⇒クラス型のオブジェクトが同一クラス型のオブジェクトで初期化される場合は、コピーコンストラクタによる
※オブジェクトの代入
⇒代入演算子


_◇引数としてオブジェクトを使う
⇒各メンバの値がコピーされて関数内に渡される。(構造体と同じ)
⇒オーバヘッドが大きい

※オブジェクトへのポインタを渡すようにすれば、オーバヘッドを小さくできる。
⇒メンバアクセスはアロー演算子による

※参照を引数としてもポインタ同様の効果が得られる。
⇒メンバアクセスはドット演算子を使える


_◇クラスを指すポインタ

※基本クラスのポインタを使うと、基本クラスだけでなく、派生クラスのオブジェクトを指すこともできる。
⇒ただし、基本クラスのポインタをつかった場合、派生クラスでオーバライドした関数があっても、通常は基本クラスの関数の方が優先される。
⇒基底クラスへのポインタ/参照は派生クラスのオブジェクトを指す、参照できるが、派生クラスへのポインタで基底クラスのオブジェクトを指す、参照することはできない。
⇒派生クラスは基底クラスの一種である。
⇒is-Aの関係(kind-of-Aの関係)
⇒public派生はインタフェースである公開部が派生クラスに引き継がれるのでインタフェース継承と呼ばれる。
⇒基底クラスのポインタで派生クラスを指すと派生クラス内の基底クラスオブジェクト部分を指すことになる。(同じ派生クラスを指すポインタがあっても異なるアドレスになる可能性がある)
⇒基底クラスのポインタは派生クラスを指すことができるが、それを評価して得られるのは派生クラスではなく基底クラス型。
⇒そのようなポインタや参照の静的な型(static type)は基底クラスとなる。
⇒typeid(ポインタもしくは参照)により静的な型が確認できる。

※仮想関数宣言
virtual function
⇒基本クラスの関数を仮想関数として宣言しておくと、基本クラスのポインタが派生クラスを指していたときに呼び出されるのは「実体」である派生クラスの関数とできる。

virtual 基本クラスメンバ関数の宣言;

※アップキャスト
up cast
派生クラスへのポインタを基底クラスへのポインタに型変換すること
public派生の場合:
│ 任意の関数でアップキャストできる
protected派生の場合:
│ 派生クラスのメンバとフレンドおよび、派生クラスからさらに派生したクラスのメンバとフレンド
private派生の場合:
│ 派生クラスのメンバとフレンドのみ

※ダウンキャスト
down cast
⇒通常はできない


_◇thisポインタ
コンストラクタやメンバ関数が所属するオブジェクトを指すポインタ
⇒*thisは自オブジェクト

this->メンバ名

などとしてクラスの中で使う

※クラスC型のオブジェクトのメンバ関数におけるthisの型は,C*型となる
⇒const, volatileされていればそれも有効

※データメンバと同じ名前の仮引数やローカル変数を宣言すると、データメンバは隠ぺいされてしまうが、this->をつけることで所属するオブジェクト内のデータメンバにアクセスすることができる。


_◇クラスメンバの参照

クラスメンバアクセス演算子
class member access operator

※「.」演算子
dot operator
⇒パブリックメンバのみ参照可能

書式)
変数名.メンバ関数名(実引数)

※クラスへのポインタからメンバにアクセスする場合は、アロー演算子を使う
arrow operator

書式)
ポインタ名->メンバ関数名(実引数)

例)
pCar->num = 1234;

※p->mは、(*p).mと等価

※アクセッサ
ゲッタとセッタ
⇒単一のデータメンバの値を取得して返却するメンバ関数=ゲッタ
⇒データメンバに値を設定するメンバ関数=セッタ
⇒データメンバとメンバ関数を同じにすることはできない

※クラスCの内部で定義された識別子mはクラスの有効範囲の中に入る。
⇒識別子mが公開属性を持っている場合、有効範囲解決演算子::を用い
│ C::m
によってアクセスできる


_◇publicとprivate

※アクセス指定子(access specifier)
⇒ラベルによりメンバへの参照方法を制御できる
public:
│ パブリックメンバにはクラスの外からアクセスできる
private:
│ プライベートメンバにはクラスの外からアクセスできない
│ 派生クラスからもアクセスできない
│ 省略した場合はprivateとなる
protected:
│ 派生クラスからはアクセスできるが、それ以外からはアクセスできない

※カプセル化
encapsulation

※情報隠蔽
information hiding
プログラムがクラスを使うために必要な最低限のクラス情報だけをプログラムに知らせる
⇒外部から参照する必要の無いプライベートメンバ
ラベルpublicが無ければ、デフォルトで全てのメンバはプライベートメンバ
⇒プライベートメンバはクラスメソッドからのみ参照できる

※インタフェース関数
interface function
プライベートなメンバへの参照を制御するクラスメソッド。代入値が適正か否か判断できる。

※クラスデータメンバのほとんどはプライベートにして保護するのが普通。インタフェース関数をパブリックにする。

※関数の引数名とクラスメンバ名の衝突
⇒クラスメンバ名の前にクラス名と大域解決演算子「::」をつけて衝突を解決する


_◇コンストラクタ関数
constructor

※クラス自身と同じ名前を持ったクラスメソッド
※オブジェクトが生成されるたびに自動的に呼び出される。
⇒通常、データメンバの初期化に使う
※値を戻せないので 戻り型を宣言しないのは勿論、voidも宣言しない

※コンストラクタ関数の定義は他のクラスメソッドと同様

定義 構文)
クラス名::クラス名(引数リスト)
{

}

例)

employee::employee(char *name, long employee_id)
{
    ...

void main(void)
{
    ...
    employee worker("AHO", 2222);

⇒オブジェクトの宣言の後にコンストラクタ関数の引数をおく。
⇒他のメソッド同様、デフォルトの引数を指定することができる。

※多重定義
コンストラクタ関数をオーバーロードすることができる。
⇒クラス定義中では、オーバーロードした関数プロトタイプを並べる

※コンストラクタが定義されない場合
⇒引数をとらないコンストラクタ(何も処理しない)が用意される
default constructor
⇒1個でもコンストラクタが定義されると、引数なしの空のデフォルトコンストラクタは用意されない。
⇒デフォルト引数を使うことで、引数のあるコンストラクタ定義を引数なしのコンストラクタとかねさせることができる。

例)ヘッダ部での定義
Date::Date(int yy, int mm=1, int dd=1);

※デフォルトコンストラクタ
default constructor
実引数を与えずに呼び出せるコンストラクタ
⇒「引数を受け取らない」ではなく、「引数を与えることなくよびだせる」コンストラクタがデフォルトコンストラクタとなる。
引数にデフォルト値を与えておけば、引数を与えることができるコンストラクタをデフォルト化することもできる。
Base::Base(int xx=99) : x(xx) { …

※変換コンストラクタ
conversion constructor
単一の実引数によって呼び出せるコンストラクタ
⇒変換コンストラクタは()形式に加えて=形式でも呼び出せる
例)
Date q = 2017;

※明示的コンストラクタ
explicit constructor
⇒暗黙の型変換を抑止するコンストラクタ
⇒単一の実引数をとる変換コンストラクタは()形式だけでなく=形式でも呼び出せるが、これを抑制する。⇒紛らわしさの抑制。


_◇コンストラクタ初期化子

通常のクラス定義では、
①メンバ関数の宣言(不定値あるいは空文字列などで初期化される)
②コンストラクタ関数内で代入
という段階を踏む

※コンストラクタ初期化子(constructor initializer)を使うことで、初期化と代入を一度に済ませることができる。
⇒初期化は、コンストラクタ初期化子の順でなく、クラス定義のデータメンバの宣言順に起こる。

例)
Human(const string& name, int h, int w)
│ : full_name(name), height(h), weight(w)
{ …


_◇コンストラクタ初期化の順番

①基底クラスのコンストラクタによって基底クラス部分オブジェクトを初期化
⇒デフォルトコンストラクタにまかせられる場合以外は明示的に
②宣言された順にデータメンバを初期化(コンストラクタ初期化子による)
⇒コンストラクタ初期化子の並びの順でなく、クラス定義における宣言の順
(基底クラスが先になる)
③コンストラクタ本体の実行

※メンバ初期化子の順を実際に初期化のおこる順に合わせるとよい。

※デストラクタによる解体はこれと逆の順序になる。


_◇コピーコンストラクタ
copy constructor

オブジェクトが他のオブジェクトで初期化される場合、そのまま単純にメンバをコピーするとすると、文字列のようなものは、同じメモリをポイントするようになってしまう
⇒オブジェクトがほかのオブジェクトで初期化される場合には、コピーコンストラクタが呼び出されるので、これを使ってメモリを確保する。
⇒オブジェクトの関数間値渡し、戻り値

※コピーコンストラクタ
受け取った同一型の引数をもとに初期化を行うコンストラクタ
⇒コピーコンストラクタを明示的に定義していないクラスに対しては全データメンバの値を単純にコピーするコピーコンストラクタがコンパイラにより自動提供される。
例)
Date temp(*this) //Dateクラス内において
⇒thisはメンバ関数が所属するオブジェクトをさすポインタなので、*thisが実体

構文)

クラス名::クラス名(const 参照型 引数)
{
    ...
}

例)
Car::Car(const Car& c)
{
    pName = new char[strlen(c.pName)+1];
    strcpy(pName, c.pName);
}

※初期化の場合はコピーコンストラクタが、代入の場合には代入演算子による処理が行われる。

※関数に実引数としてオブジェクトを与える場合、実引数によって仮引数が初期化されるので、コピーコンストラクタを準備する必用があるかもしれない。
⇒コピーコンストラクタの明示的な多重定義

コピーコンストラクタの多重定義の形式)
Class名::Class名(const Cクラス名& 引数)
⇒引数として受け取ったオブジェクトへのポインタ &引数と自分自身へのポインタthisの等価性を判定すること
⇒等しければ、未初期化の自分自身で初期化することになるので適当にNULLポインタなどを代入する。

※形式だけを非公開部で宣言することで、利用者が呼び出せないようにできる。


_◇デストラクタ関数
destructor

※オブジェクトが削除されるたびに自動的に実行される
⇒割り当てたメモリ(newしたもの)を開放(delete)するときに便利
⇒オブジェクトの生存期間が尽きる⇒オブジェクトそのものの領域は開放される
⇒コンストラクタ内でnewした領域は自動開放されない⇒デストラクタで開放
⇒引数を受け取らない
⇒多重定義できない

※プログラム終了時⇒オブジェクト削除⇒自動的にそれぞれのデストラクタが呼び出される

※デストラクタもオブジェクトのクラスと同じ名前を持つ。ただし、名前の前に「~」がつく

構文)
クラス名::~クラス名()
{
│ …

例)
~class_name(void)
{
│ …

※デストラクタに引数は渡せない
⇒デストラクタのオーバロードはできない

※派生クラスのオブジェクトが破棄されるとき
⇒先に派生クラスのデストラクタが呼び出される
⇒コンストラクタの順番と逆
⇒基本クラスのポインタを使って派生クラスをアクセスしているときは注意
(⇒基本クラスのデストラクタにvirtualをつけておく必要がある)


_◇演算子のオーバーロード
operator overloading

※新しく定義された型(クラス)に対して、演算子をオーバーロードすることができる
⇒演算子関数(operator function)
⇒演算子は、operator 演算子 という名前のメンバ関数もしくは非メンバ関数として定義
⇒メンバ関数と、非メンバ(フレンド関数)で呼び出され方が異なる。

※演算子関数の動作は自由。しかし、演算子の本来の仕様と類似した仕様とするのが原則。

※新しい演算子を作ることはできない。
※演算子の優先度や結合規則を変更することはできない
※代入演算子 = は非メンバ関数として定義できない
※演算子☆を定義しても、複合代入演算子☆=は定義されない
⇒別途定義必要
⇒A=A☆BとA☆=Bが等価かどうかはユーザの定義による
※&&と||は、素のままでは短絡評価だが、オーバロードした場合は短絡評価されない
⇒クラス型に論理演算子を定義することは推奨されない
※3項演算子? : は定義できない。

※単項演算子
①引数を受け取らないメンバ関数として定義
│ 返却値型 クラス::operator☆()
│ ⇒☆a は a.operator☆()と解釈される
│ ⇒後置形式の++と–のみ、例外的にダミーのint型引数を受け取る
②引数1個の非メンバ関数
│ 返却値型 operator☆(const C a&)
│ ⇒☆aは、operator☆(a)と解釈される

※2項演算子
①引数1個のメンバ関数として定義する
│ 返却値型 クラス::operator☆(Type b&)
│ ⇒a☆bは a.operatro☆(b)と解釈される
②引数2個の非メンバ関数として定義する
│ 返却値型 operator☆(const C a&, Type b&)
│ ⇒a☆bは、operatro☆(a, b)と解釈される

増分演算子++と減分演算子–の演算子関数は前置形式と後置形式を区別して多重定義
⇒返却値 クラス名Cの場合通常は
前置演算子は C&型
例)
操作した後の自分自身への参照を*thisとして返す

Data& Date::operator++()
{
    if (d < days_of_month(y, m))
        d++;
    else {
        if (++m > 12) {
            y++;
            m = 1;
        }
        d = 1;
    }
    return *this;
}

後置演算子は C型
操作する前の値を返す必要がある
①自分自身 *thisのコピーを作業変数tempに保存
②前置演算子で操作
③保存してあった操作前の値tempを返却する
⇒一般的に前置形式よりも後置形式の方が高コストとなる

※クラスオプジェクトに対する前置、後置++演算子は
++x ⇒ x.operator++()
x++ ⇒ x.operator++(0)
のように呼び出される。後置演算子についてはダミー引数0が渡されるので判断がつく
⇒以下の例のように、ダミーの仮引数については名前を与えなくてもよい
例)
Date operator++(int)

※2項演算子
メンバ関数として定義された2項演算子は、
①左オペランドに対してその演算子関数が呼び出される。
②右オペランドが引数として渡される。

※クラスの演算子をオーバーロードしても他の型の演算子は変更されない

例)演算子の定義
void operator +(char *);

例)演算子の関数定義
void string::operator +(char *str)

※演算子の用法を、あるクラスの特殊な形のメンバ関数として定義することで、あるクラスのオブジェクトに対する演算子を定義する

構文)
演算子関数 operator function
2項演算子をメンバ関数としてオーバーロードする

戻り値の型 operator演算子記号(引数1);

例)
p1 + p2

オペレータ関数により
p1 .operator演算子記号(p2);
であるとする。

⇒ただし、上記の形式では、p1が、該当するクラスのオブジェクトでなければならない

※オーバーロードできない演算子
. メンバ演算
.* メンバへのポインタ演算
:: 大域解決
?: 条件式演算

①以下の演算子はオーバーロード不可
. :: .* ?: sizeof
②通常の演算子記号以外の新たな記号を演算子とはできない
③基本型に対する用法のオーバーロードはできない
④演算子のオペランド型は変更できない。単項用の記号は単項、2項用の記号は2項。
⑤優先順位も変更できない
⑥演算子にデフォルト引数を定義することはできない

※代入演算子=は、オーバーロードしなくても、オブジェクトに対して作用させる(全データメンバコピー)ことができる。オーバーロードすることもできる。
⇒代入演算子がオーバーロードされていないときに、クラスオブジェクトが同じ型のクラスオブジェクトに代入されるならば、全データメンバの値がコピーされる。
⇒オブジェクトに他のオブジェクトの値が代入されるような場合に、メモリの確保の必要がある場合には、代入演算子をオーバーロードしなければならない
⇒代入演算子のオーバーロードは、メンバ関数としてでなければならない。
⇒代入を行う場合には、まず自分自身による代入を除く。
⇒代入を行う場合には、既に代入先のオプジェクトが存在しているので、既に確保されているメモリを破棄し、新たに確保する必用がある。

例)

Car& Car::operator=(const Car& c)
{
    if (this != &c) {
        delete[] pName;
        pName = new char[strlen(c.pName)+1];
        strcpy(pName, c.pName);
    }
    return *this;
}

※関数から戻り値としてオブジェクトが与えられ、それが他のオブジェクトに代入されるならば、代入演算子のオーバーロードが必要ないか検討すること。

※等価演算子==と!=は自動提供されない。値を比較する必要があるなら、自分で定義

※単項演算子のオーバーロード
引数をとらないメンバ関数として定義する(前置の場合)

戻り値の型 operator演算子名();

※戻り値として *this を指定する。
⇒this はメンバ関数を呼び出したオブジェクト自身へのポインタ

※後置演算子の場合は、前置と区別をつけるためだけの引数をひとつもつ演算子関数としてオーバーロードする
例)
Point Point::operator++(int d)

※演算子関数では、ポインタを引数とすることはできないが、参照は引数とすることができる。
⇒オブジェクトそのものを引数とするより参照の方が処理速度が向上する場合がある。


_◇挿入子のオーバーロード

inline std::ostream& operator<<(std::ostream& os, const Shape& s)
{
    return os << s.to_string();
}

※非メンバ関数として定義する挿入子<<が多相的に振る舞う様にするためには、
表示内容を文字列として返却する純粋仮想関数の返却値を出力するように定義する。

例)

class 基底クラス {
    virtual std::string to_string() const = 0;

};

class 派生クラス {

std::string to_string() const {
    std::ostringstream os;
    os << "Rectangle(width:" << width << ", height:" << height << ")";
    return os.str();
}

};

inline std::ostream& operator<<(std::ostream& os, const 基底クラス& s)
{
    return os << s.to_string();
}


_◇代入演算子の多重定義

※代入演算子を多重定義しない場合、全データメンバをコピーする演算子が自動的に用意される。
⇒そのまま全データメンバをコピーしたのではまずい場合は多重定義が必要

代入演算子の多重定義の形式)

Class名& Class名::operator=(const Class名&)
{
    ...
    return *this;
}

⇒メンバ関数が所属するオブジェクトへの参照を返す
⇒返却値を参照としておけば、代入式の左辺にもおける
⇒返却値をvoidとすると
│ x = y = z;
のような場合に
│ x.operator=(y.operator=(z));
のように解釈されるので、y.operator=(z)がvoidとなってエラーとなる

※自己代入に対する処理を含めること
⇒thisポインタと、代入元オブジェクトへのポインタが等価なら自己代入

※形式だけを非公開部で宣言することで、利用者が呼び出せないようにできる。


_◇添字演算子の多重定義
配列の各要素に手軽にアクセスできるようにする
⇒返却値型は、組み込み型ではなく参照型とする
⇒参照型とすることで、代入式の左辺に置くことができるようになる。

※constオブジェクトに対する添字演算子の適用のためconst版の添字演算子も多重定義するとよい。


_◇挿入子、抽出子の多重定義
挿入子<<
抽出子>>
⇒多重定義により入出力が容易となる


_◇constメンバ関数
メンバ関数の定義の後ろにconstをつけるとconstメンバ関数となる

例)
const int& operator[](int i) const { return vec[i]; }

※constでないメンバ関数は定値オブジェクトに対して呼び出すことができない
⇒constメンバ関数は定値オブジェクトに対して呼び出せる。


_◇変換関数とユーザ定義変換
conversion function

operator 型名
と名付けられたメンバ関数。
⇒所属するオブジェクトの値を、任意の型に変換して返却するメンバ関数
⇒関数名そのものが返却値型を表す⇒返却値型は指定できない。引数も受け取れない。
⇒通常 const メンバ関数として実現する。
⇒変換関数を提供することで、明示的キャスト、暗黙裡のキャストが行えるようになる。
※ユーザ定義変換がそろうと2つの型間の型変換が相互に可能となる
user-defined conversion
①変換コンストラクタ
②変換関数

※クラスの型変換
クラスの中に変換関数(conversion function)を準備しておくと、クラスの値を、別の型の値に変換することができる。

構文)
operator 型名()

例)
operator int() {return num; }

※変換関数があれば、キャスト演算子を使っても、使わなくても、型変換が行われる。

※引数1つのコンストラクタは変換関数の逆の機能をもつ
⇒変換コンストラクタ(conversion constructor)

構文)
クラス名::クラス名(引数)
⇒変換コンストラクタが定義してあると、自動的に型変換が行われる。


_◇静的メンバ
static member

オブジェクトに関連づけられておらず、クラス全体に関連づけられているメンバ
⇒クラス宣言内でメンバの宣言するときに static 記憶クラス指定子をつける
⇒オブジェクトのコンストラクタ中では初期化しない。
⇒関数の外側で初期化
(例外:静的データメンバがconstつきの整数あるいは列挙型のときのみ、クラス定義の中で静的メンバに初期値を与えてもよい)
⇒クラス全体のオブジェクトの数など、クラス全体で扱うデータを格納する
⇒そのクラスに所属する個々のオブジェクトの状態と無関係の手続き

例)

Class Car{
    public:
        static int sum;
        static void showSum();
    ...
}

int Car::sum = 0;
void Car::showSum()
{
    ...

※静的データメンバ
static data member
⇒クラスのオブジェクトがいくつ生成されても生成されなくても、静的データメンバの実体は、1個だけ作られる。
⇒クラス定義の中の静的データメンバの宣言にはstaticをつける
例)
static int dmax[];
⇒実体は、クラス定義の外側でstaticを付けずに定義する。
クラス名::データメンバ名
例)
int Date::dmax[] = {31, 28, 31, 30, 31, 30, 31, 31, 30 , 31, 30, 31};
⇒公開属性を持つ場合は、通常、クラス名::データメンバ名でアクセスできるが、オブジェクト名.データメンバ名でもアクセスできる。

※スタティック関数(静的メンバ関数)
static member function
static なメンバ関数
⇒オブジェクトが生成されていなくても呼び出すことができる。
⇒クラスの外にあるデータを操作するメソッドなど
⇒静的メンバ関数内では、通常のメンバにアクセスすることはできない
⇒静的メンバ関数の定義はクラス定義の中でも外でもよい(データメンバとは異なる)
⇒クラス定義の外で定義する場合は、staticを付けず
クラス名::メンバ関数名
で定義する
⇒静的メンバ関数は特定のオブジェクトに属さないのでthisポインタを持たない

※クラスの外部から公開属性の静的メンバ関数を呼び出す形式
クラス名::メンバ関数名(…)
⇒オブジェクト名.メンバ関数名(…)でも呼び出せる

※同一の名前を持つ静的メンバ関数と非静的メンバ関数を多重定義してもよい

※静的メンバを含むクラスから派生したクラス
⇒基底クラスの静的メンバをそのまま静的メンバとして継承する
⇒静的メンバの実体は1つなので、共有されることになる


_◇データメンバ
同じクラスに属する複数のオブジェクトにより共有されるクラスメンバ
⇒データをstatic宣言
│ public … オブジェクトが無くても使用可能
│ private … オブジェクト必要
⇒かつ、クラスの外でメンバを大域変数として宣言

例)

private:
    static int shared_value;
...

int class_name::shared_value;


_◇入れ子クラス
nested class

※別のクラス内で定義されたクラス
⇒上位のクラス内での利用を前提とする

クラスX内のクラスYは
│ X::Y
でアクセスする。


_◇継承
inheritance

基本クラス base class
継承 inherit, inheritance
派生クラス derived class

基本クラスの性質を派生クラスが継承する
⇒新しくクラスを作成すること=クラスを派生する(extends)
派生(derive)
⇒基本クラスを派生して派生クラスを宣言する
⇒派生クラスは基本クラスのメンバを継承する
⇒派生クラスでは、基本クラスから継承したメンバと派生クラスで追加したメンバを同様に呼び出すことができる。

※派生元のクラスの呼び名
基底クラス(base class)/上位クラス/親クラス/スーパークラス(Java式)

※派生したクラスの呼び名
派生クラス(derived class)/下位クラス/子クラス/サブクラス(Java式)

派生クラスの宣言構文)

class 派生クラス名 : [アクセス指定子] 基本クラス名
{
    派生クラスに追加するメンバの宣言
};

クラス宣言例)

class manager : public employee {

※派生クラスからは基本クラスのプライベートメンバを直接参照することはできない

※派生クラスの中では、基本クラスのプライベートでないメンバは、自分のクラスの中に定義があるものとして参照できる。

※protected メンバ
派生クラスからはpublicメンバのように参照できる
他のクラスからはprivateメンバのように参照できない

※派生クラスのコンストラクタは基本クラスのコンストラクタを呼び出さねばならない
⇒派生クラスのコンストラクタ関数の後にコロンをつけ、適切な引き数とともに基本クラスのコンストラクタ名を指定する

例)

manager::manager(char *name, char xxx) : employee(name)
{
    ...
}

※派生クラスのメンバ名が基本クラスのメンバ名と同じになってしまった場合、派生クラスの関数中では派生クラスのメンバが使われる
⇒基本クラスのメンバを参照する場合には
│ 基本クラス名::メンバ名
のようにスコープ解決演算子を使う
⇒派生クラスでのメンバ関数のオーバーライド
overriding
基本クラストまったく同じ関数名、引数の型と数のメンバ関数を定義できる
⇒派生クラスの関数がとって代わることができる。

※コンパイラによって自動定義されるデフォルトコンストラクタ、デフォルトデストラクタ、代入演算子も各クラスの資産として継承の対象となる。
⇒引数のあるコンストラクタとデストラクタは継承されない。
⇒基底クラスの引数のあるコンストラクタはコンストラクタ初期化子により明示的に呼び出す必要がある。

※フレンド関係は派生によって継承されない。

※派生クラスのコンストラクタ
何も指定しないと派生クラスのコンストラクタの先頭で、基本クラスの引数のないコンストラクタ(デフォルトコンストラクタ)が呼び出される。
⇒派生クラスについてコンパイラによって自動的に定義されるデフォルトコンストラクタも、基底クラスのデフォルトコンストラクタを呼び出す。
⇒基底クラスにデフォルトコンストラクタが無い場合、派生クラスのデフォルトコンストラクタを作成することができないため、派生クラスのコンパイルができない。
(コンストラクタを定義しないクラスは、そのクラスの直接基底クラスがデフォルトコンストラクタを持っていなければコンパイルエラーとなる)
⇒基本クラスから継承したメンバの初期化が行われる。
⇒呼び出す基本クラスのコンスタクタを指定する構文

派生クラス名::派生クラスコンストラクタ(引数リスト):基本クラスコンストラクタ(引数リスト)
{
    派生クラスコンストラクタの本体定義
}

コンストラクタ定義例)

class Derived : public Base {
    int x;
public:
    Derived(int aa, int bb, int xx) : Base(aa, bb), x(xx) { ... }

⇒これによりBaseクラスのBase(int aa, int bb)コンストラクタが呼び出される。
⇒メンバ初期化子では初期化できるのは直接基底クラスまで。間接基底クラスには不可。

※クラス継承時の参照の制御
継承する基本クラス名の前におかれたpublic, privateなどのアクセス指定子により、基本クラスの継承のしかたが変わる。
⇒継承した資産を外部に公開するかどうかは継承とは別問題
public派生、protected派生、private派生
⇒アクセス指定子を省略するとprivate派生となる
⇒structで定義した派生クラスはアクセス指定子省略でpublic派生となる
⇒基本クラスでprivateなものは当然継承クラスでもアクセスできない

例)
Class RacingCar : public Car{
│ …

①public 基本クラスのpublicメンバは派生クラスでもpublic
│  基本クラスのprotectedメンバは、派生クラスでもprotected
②protected 基本クラスのprotectedメンバは、派生クラスでもprotected
│  基本クラスのpublicメンバは、派生クラスではprotected
③private 基本クラスのprotected/publicメンバは全てprivate

public派生はis-Aの関係となるが、private派生やprotected派生はそうならない。
⇒is-Aの関係、派生クラスは基本クラスの一種
⇒しかし、基底クラスのメンバも派生クラスに含まれてはいる
⇒private派生、protected派生は実装継承
is-implemented-in-terms-of is-implemented-using
(派生クラスは基本クラスを使って実装されている)

※クラス階層図
階層図では、派生クラスから基底クラスに向かって矢印をひく
⇒派生クラスは基底クラスを知っているが、基底クラスは派生クラスのことを知らない

※間接派生と直接派生
直接の親=直接基底クラス(direct base class)
直接の親ではないが先祖=間接基底クラス(indirect base class)

※基底クラス部分オブジェクト
base class sub-object
派生クラス型のオブジェクトに含まれる基底クラス型のオブジェクト
⇒基底クラスの非公開メンバは、派生クラスから直接アクセスできないが、基底クラス部分オブジェクト無いに含まれる
⇒基底クラスから継承したメンバ関数は、派生クラス型のメンバ関数およびフレンドの中では、あたかも派生クラス型のメンバ関数であるかのように利用できる。

※特殊メンバ関数
派生クラス内で特殊メンバ関数(コピーコンストラクタ、デストラクタ、代入演算子)が定義されなければ、基底クラスのものと実質的に同一の働きをする関数が自動的に定義される。
⇒コンパイラ自動定義の特殊メンバ関数は、inlineかつpublic.

※派生クラスX内で代入演算子が定義されなければ以下の形式の代入演算子が自動定義される。
X& X::operator=(const X&);


_◇using宣言によるアクセス権の調整
using declaration

書式
│ using クラス名::メンバ名;
⇒明示的な宣言を公開部あるいは限定公開部で行うことで、基底クラスから継承した資産のアクセス権をを公開、あるいは限定公開に変更できる
⇒派生クラスでアクセスできなくなっているメンバについては不可能

※初期のC++では usingが使えず、アクセス宣言(access ceclaration)によった。
書式
クラス名::メンバ名;


_◇汎化と特化

汎化 generalization
│ 複数のクラスに共通する部分を一般化

特化 specialization
│ 基底クラスに派生クラスの機能を追加して特化


_◇スライシング
※基底クラスオブジェクトに対して、派生クラスオブジェクトを代入されると、派生クラス内の「基底クラス部分オブジェクト」の部分がコピーされる。
⇒派生クラスに特有の情報は切り捨てられて失われる。
⇒slicing
⇒派生クラスオブジェクトに基底クラスオブジェクトを代入することはできない
(派生クラスに特有の情報を決定できないため)


_◇隠蔽
hide

基底クラスのメンバ関数と同一名のメンバ関数が派生クラス内で定義されると、基底クラスのメンバ関数は隠ぺいされる。
⇒関数名が同一であれば、シグネチャが異なっても隠蔽される。

※隠蔽されているメンバは
│ 基底クラス名::メンバ名
によってアクセス可能


_◇仮想関数
virtual function

仮想関数はメンバ関数宣言の前にvitualキーワードをつける
仮想関数を派生クラスで再定義することで、クラスの振る舞いをカスタマイズできる

※基底クラスに仮想関数を定義する。
⇒基底クラスの仮想関数と同じシグネチャを持つメンバ関数はvirtualをつけなくても仮想関数とみなされる。

※仮想関数の場合、基底クラスへの参照を通じて呼び出される関数は、コンパイル時に静的に決定されず、プログラムの実行時に動的に決定される。

例)
Bclass objB;
objB.Action();
│ ⇒Bclass のActionが呼び出される

DelivBclassはBclassの派生クラス
DelivBclass objD;
objD.Action();
│ ⇒DelivBclass のActionが呼び出される

Bclass * pb = new DelivBclass();
pb->Action();
│ ⇒DelivBclass のActionが呼び出される
│ みかけはBclassだが、]実際はDelivBclassなので、そちらが呼び出される

※非仮想関数
呼び出すべき関数をコンパイル時に決定できる
│ 静的結合 static binding
│ 早い結合 early binding
※仮想関数
呼び出すべき関数は実行時に決定
│ 動的結合 dynamic binding
│ 遅い結合 late binding (遅延束縛)
(binding=束縛)

※オーバライド
override
基底クラスの仮想メンバ関数を派生クラスで同形式の関数を定義して上書きすること
⇒オーバライダ overrider(上書き関数)
⇒基底クラスのメンバ関数が仮想関数でなければオーバライドにはならない。
⇒仮引数の型が異なれば、オーバーロード(多重定義)であり、非仮想となる。

※返却値のルール
①オーバライドされる関数の返却値と同一
②2つの関数のクラスで共変的(convariant)
⇒異なってもよい。

※2つの関数の返却値が共変的
関数Derived::fが関数Base::fをオーバライドしているときに以下の全条件が成立
①両者ともにクラスへのポインタあるいはクラスへの参照を返す
②Base::fの返却値の型のクラスがDerived::fの返却値の型のクラスと同じか、アクセス可能な基底クラス
③ポインタまたは参照が同一のcv修飾
⇒const修飾とvolatile修飾
⇒共変的の対語 反共的(contravariant)

※静的メンバ関数を仮想関数とすることはできないし、オーバライドもできない。

※仮想関数テーブル
virtual function table
⇒多相的クラスに用意され、各オブジェクトにはそこへのポインタが埋め込まれる。


_◇仮想デストラクタ
virtual destructor

基底クラスのポインタが派生クラスのオブジェクトを指している場合、基底クラスのポインタの指すオブジェクトをdeleteで解放しても、基底クラスのデストラクタのみが読み出され、派生クラスのデストラクタは呼び出されない。
⇒メモリリーク

※基底クラスのデストラクタを仮想関数として定義すれば、基底クラスも派生クラスも多相的クラスとなり、上記のような場合でも派生クラスのデストラクタも呼び出される。
⇒派生クラスのデストラクタが先に呼び出され、後から基底クラスのデストラクタが呼び出される。
⇒他のクラスの基底クラスとなる可能性があるクラスのデストラクタは仮想デストラクタとして定義しておくとよい。
(コンストラクタは仮想関数にはできない)
⇒純粋仮想デストラクタ pure virtual destructor
⇒純粋仮想デストラクタには本体が必要。本体はクラス外部で宣言する。


_◇多相的クラスとポリモーフィズム
polymorphic class = polimorphic object
polymorphism

※仮想関数を1個でも有するクラス
⇒クラスに所属するメンバ関数の内の一つが仮想関数となるだけで、そのクラス型の参照とポインタの性質が変わる。
⇒多相的クラス型への参照/ポインタを通じた仮想関数の呼び出しでは、呼び出される関数の決定が動的な型に基づいて行われる。

※非多相的クラスの場合、
│ ポインタptrの型は静的な基本クラス mmm*
│ 参照refの型は静的な基本クラス mmm&
※多相クラスで派生クラスのポインタや参照を基本クラスへのポインタ、参照に代入している場合
│ ポインタptrの型は動的なクラス mmD*
│ 参照refの型は動的なクラス mmD&

※多相クラスと動的な型を利用することで可能となること
①基底クラス型へのポインタ/参照を通じて、異なるクラス型のオブジェクトに対して同一のメッセージを送ることができる。
②メッセージを受け取ったオブジェクトは自分自身の型に応じた適切な行動を起こす

※多相的クラス型のオブジェクトには仮想関数テーブルへのポインタが埋め込まれるため、そうでないクラスよりその分だけ大きくなる。

※メンバ関数の動的結合の実現は、呼び出すべき関数へのポインタをまとめた仮想関数テーブルへのポインタとして実現される。
⇒プログラム上からは直接アクセス不能なオブジェクト
⇒全ての多相オブジェクトに埋め込まれる。


_◇オブエジェクト指向プログラミングの三大要素
object oriented programming

※3大要素

①クラス
②継承
③多相性

※オブジェクト
値を表現するための記憶域
⇒他のオブジェクトを部分オブジェクト(sub-object)として含むことができる
│ 配列要素
│ メンバ部分オブジェクト member sub-object
│ 基底クラス部分オブジェクト base class sub-object
⇒他のオブジェクトの部分オブジェクトになっていないオブジェクト
│ 総体オブジェクト complete object

※最派生クラス
most derived class
⇒基底クラス部分オブジェクト以外の、総体オブジェクト、データメンバ、配列要素の型。
⇒最派生オブジェクト(most derived object)


_◇多重継承
multiple inheritance

※単一継承 single inheritance

基本クラスを複数指定できる。基本クラス名はカンマで区切る。
⇒2つ以上の基本クラスのメンバを継承できる

構文)

class 派生クラス: アクセス指定子 基本クラス1, アクセス指定子 基本クラス2...{
    ...
};

⇒基底指定子並び(base specifier list)
アクセス指定子 基本クラス1, …
⇒アクセス指定子は個々の基底クラスに対して指定できる
⇒指定しないとclassはprivate, structはpublic派生となる
⇒基底クラスとして同一のクラスを直接2回以上指定することはできない
⇒間接基底クラスとして、同一のクラスが2個以上含まれてもよい。
(先祖をたどると同じだった場合)

宣言例)
class C : public A, private B {

};

宣言例)
class computer : public computer_screen, public mother_board

※コンストラクタ関数では、複数の基本クラスのコンストラクタ関数を呼び出す

computer::computer(char *name, char *screen, int processor) :
    computer_screen(screen),
    mother_board(processor)

※2つの基本クラスで同じ関数名とシグネチャの関数が存在しているとコンパイルできない
⇒スコープ解決演算子によって解決できる。

例)
drv.Base1::func();
⇒同一名のメンバをもつ基底クラスを多重継承したクラスでは、有効範囲解決演算子::を用いてメンバを使い分ける

※派生クラスを基本クラスとしてさらに派生クラスを作っていくことで継承の連鎖構造を生成することができる。

※連鎖構造
間接基本クラス-直接基本クラス-派生クラス
indirect base class – direct baseclass – derived class

※仮想基本クラス
virtual base calss
多重継承する複数の基本クラスが、さらに間接基本クラスをもち、かつその間接基本クラスが同一の場合は、どちらの直接基本クラスを通じて継承したのか決定できず、コンパイルできない。
⇒直接基本クラスが間接基本クラスを継承するときに、その継承に virtual 指定しておくと、その間接基本クラスは仮想基本クラスとなる
⇒派生クラスの中に、その間接基本クラスは一つだけ持つようにできる

構文)

class 派生クラス : アクセス指定子 virtual 基本クラス {
    ...
};


_◇多重継承における基底クラス部分オブジェクトの初期化順序

①多重継承によってつくられたクラスの基底クラス部分オブジェクトは基底指定子並びの宣言順に初期化される。

②その後でメンバ部分オブジェクトが初期化される。

※初期化の順序は、コンストラクタ初期化子の宣言順ではなく、クラス宣言の基底指定子並び順
⇒可読性を高めるために、コンストラクタ初期化子の宣言順をクラス宣言の基底指定子並びにあわせるとよい。

_◇多重継承におけるクロスキャスト
cross cast

※dynamic_cast演算子を利用し、多重継承を行った親同士(へのポインタ間あるいは参照間の)キャスト(クロスキャスト)が可能となる
⇒ダウンキャストの応用


_◇多重継承例

$ cat multiple.cpp
#include <iostream>

using namespace std;

class Base1 {
    public:
        int x;
        Base1(int a = 0) : x(a) {
            cout << "Base1::x=" << x << " initialized.\n";
        }
        void print() { cout << "Base1:x=" << x << '\n'; }
};

class Base2 {
    public:
        int x;
        Base2(int a = 0) : x(a) {
            cout << "Base2::x=" << x << " initialized.\n";
        }
        void print() { cout << "Base2:x=" << x << '\n'; }
};

class Derived: public Base1, public Base2 {
    int y;
    public:
        Derived(int a, int b, int c) : y(c), Base2(a), Base1(b) {
            cout << "Derived::y=" << y << " initialized.\n";
        }
        void func(int a, int b) {
            Base1::x = a;
            Base2::x = b;
        }
};

int main()
{
    Derived z(1, 2, 3);
    z.func(1, 2);
    z.Base1::print();
    z.Base2::print();
}


_◇仮想派生
virtual derivation

多重定義の複雑さを回避するための仕組み

※派生クラスを定義するときに、基底クラスに対して virtualを付加して定義
⇒基底クラスは仮想基底クラス(virtual base class)となる

※ある基底クラスを複数個もつように派生を行っても、仮想基底クラスの実体は複製されない。
⇒派生クラスのオブジェクトに、仮想基底クラス型の部分オブジェクトは1個のみ含まれる。
⇒同一型のクラスを仮想基底クラスと非仮想基底クラスの両方として持った場合、仮想基底クラスは1個だけ、非仮想基底クラスは派生ごとに1個含まれる。

※あるクラスの基底クラスのvirtual宣言は、あるクラスを継承して新しいクラスを作るときに、あるクラスの基底クラスのメンバを重複させるなという指示

※仮想基底クラス部分オブジェクトは、すべての非仮想基底クラス部分オブジェクトよりも先に初期化される。

※仮想派生によってつくられたクラス型のオブジェクトの内部にはポインタが埋め込まれている。
⇒通常の基底クラスの部分オブジェクトが内部に含まれるのと異なり、仮想基底クラスの部分オブジェクトにあたるものは外にあって、ポインタでさされている。(一般的な実装の場合)


_◇抽象クラス
abstract class

※ある抽象的な概念を示す設計図としてのクラスであり、具体的な実装を行う派生クラスに対する基底クラスとなるもの
①オブジェクトを生成できない、生成すべきでない
②メンバ関数の本体が定義できず、その内容は派生クラスで具体化すべきもの
⇒下位クラスをグループ化して多相性を有効活用するためのクラス

※純粋指定子
pure specifier

※純粋仮想関数
pure virtual function
メンバ関数に純粋指定子 =0 をつけて宣言する
⇒純粋仮想関数は本体を与えずに定義するのが一般的だが、本体を定義することも可能
⇒本体を定義する場合はクラス定義の外で行う。
⇒本体が定義された公開属性をもつ純粋仮想関数は、外部から呼び出せる。

構文)
virtual メンバ関数の宣言 = 0;

例)
virtual void show() = 0;

※抽象クラス
純粋仮想関数を1個でも有するクラス
⇒クラス宣言内に、純粋仮想関数を一つでも持つクラスは、オブジェクトを作成することができない。
⇒抽象クラスの変数は宣言できない
⇒動的にオブジェクトを作成することもできない
⇒仮引数の型にはなりえない
⇒返却値の型にはなりえない
⇒抽象クラスのポインタを準備することはできる
⇒抽象クラスのポインタで派生クラスのオブジェクトを指させることで、抽象クラスの純粋仮想関数と同じ名の派生関数の実体のある関数を呼び出して使うことができる。
⇒派生クラスを統一的に扱う仕組みを提供している

※メンバ関数を純粋仮想関数と宣言
⇒子や孫などの下位クラスに対してメンバ関数のオーバライドを強要

※抽象クラスの純粋仮想関数の内容を、派生クラスでオーバーライドすることで、派生クラスでオブジェクトを生成できるようになる。
⇒オーバライドを行わないとその関数は純粋仮想関数のまま継承される⇒よってその派生クラスも抽象クラスとなる

※抽象クラスのコンストラクタ
⇒直接呼び出してオブジェクトを生成することはできない
⇒派生クラスのコンストラクタから間接的に呼び出される。

※抽象クラスへのポインタが派生クラスを指すとき、ポインタに対してdeleteすると呼び出されるのは抽象クラスのデストラクタとなる。⇒派生クラスで記憶域を動的に確保する場合にはデストラクタが必要

※そのクラス内で本体が定義されていない純粋仮想関数を呼びだす式がコンパイルエラーとなることはない。
⇒実行時に動的結合によりポインタの指す先の派生型のメンバ関数が呼び出される。

※挿入子<<
関数の第一引数ostream&型のため、クラスのメンバ関数としては定義できない
非メンバ関数でありながら多相的にふるまわないとならない
⇒operator<<そのものは非多相的、しかし
⇒純粋仮想関数を呼びだすことで解決する。


_◇抽象基底クラス
abstract base class

仮想関数だけで構成されるクラス
⇒定数でないデータメンバを含まない
(定数と列挙型宣言を含めてもよい)
⇒javaのinterfaceに相当する

※すべてのメンバ関数が純粋仮想巻子
⇒「どのように操作されるのか」だけが定義されたクラス

※抽象基底クラステンプレートから派生したクラス群
⇒互いに置換可能な仕様に定義しておく
⇒状況に応じてクラスを差し替えることが可能になる


_◇オブジェクトのクラスを調べる

※実行時型情報 RunTime Type Information
RTTI

※typeid 演算子
#include <typeinfo>

typeid(クラス変数)
⇒type_infoというクラスのオブジェクトへの参照(RTTI)が得られ、これを比較(type_infoクラスでオーバロードされている==演算子)すれば2つのクラス変数の指すオブジェクトが同じ型のクラスか否かを判断できる。

※typeid(クラス変数).name()
⇒クラス名を得ることができる。


_◇フレンド
friend

※プライベートなクラスメンバ
インターフェース関数を使わないとメンバを参照できない
インターフェース関数の呼び出し処理のオーバヘッドをさけるため、特定クラスから他のクラスのプライベートメンバを直接参照する⇒フレンド

※フレンドクラスの定義
あるクラスがそのクラスのフレンドであることを定義するにはキーワードfriendによりそのクラス名を指定する
例)

class book {
  public:
    book(char *, char *, char *);
    void show_book(void);
    friend librarian;
...

※フレンドのプライベートメンバの参照

※フレンドからの参照の限定
フレンドの限られた関数だけが参照できるように制限することができる
⇒あまりフレンド関数を定義しすぎると、カプセル化の意味がなくなるので注意

例)
friend char *librarian::get_catalog(book);
⇒friend文に完全な関数プロトタイプが含まれる
⇒このクラス定義に先立って librarian は定義されなけらばならない
⇒しかし、その librarian の中でこのクラス「book」が参照される
⇒1行のクラス定義をつかって、後で定義するクラスであることをコンパイラに伝える

例)
class book;


_◇フレンド関数
friend function
⇒随伴関数

※ヘッダ部の関数宣言に friendを与える
⇒クラス定義の中でfrinedをつけて宣言すると、そのクラスの非公開メンバにアクセスできる権限が与えられる。
※ソース部の関数定義には friendはつけない
⇒クラスの外部で定義される非メンバ関数か、別のクラスのメンバ関数

※クラスCのフレンド関数は、そのクラスのオブジェクトに対して起動されるわけではない
⇒ドット演算子での呼び出し不可
⇒thisポインタをもたない
⇒しかし、非公開メンバにアクセスできる

※非メンバ関数として定義された2項演算子
⇒左オペランドが第一引数、右オペランドが第2引数


_◇フレンド関数による演算子のオーバーロード
※フレンド関数
friend function

構文)
2項演算子をフレンド関数としてオーバーロードする場合
friend 戻り値の型 operator演算子記号(引数1, 引数2);

例)
クラス宣言の中で
│ friend Point operator+(int a, Point p);
⇒クラスの中で宣言するが、クラスのメンバ関数ではない
⇒しかし、クラスのprivateメンバにアクセスできる特別な関数

⇒関数本体の定義
Point operator+(int a Point p)
{
│ …
⇒メンバ関数ではないので、スコープ解決のためのクラス名::はつけない


_◇クラステンプレート
class template
異なる型を扱うことのできる総称的なクラスを定義することができる。
⇒異なった型ごとにクラスを複写して書き直す手間を減らすことができる

テンプレートクラス
template class

例)クラステンプレートの宣言例

template<class T, class T1>
class array {
… (以下で, T, T1を使って定義)
⇒クラス中で扱う型名を特定せずにテンプレート引数として記述する。
⇒<>内がテンプレート仮引数 (template parameter)
⇒クラステンプレートの定義を関数の中に置くことはできない

※<class Type> テンプレートにおいてコンストラクタの仮引数を
Type& 引数名 = Type()
と宣言すれば、実引数が省略された場合にはTypeに受け取った型のデフォルトコンストラクタを呼び出した値を受け取ることにできる

※コンストラクタ、デストラクタの名前は単にclassの後で宣言した名前でよい
※クラステンプレート内のそれ以外の箇所で、自クラスの型を参照する必要がある場合には、
│ クラス名<型>
と記述する。
⇒型を引数として受け取るクラス型をパラメータ化された型(parameterized type)と呼ぶ。

例)クラス関数の定義例
※クラステンプレートと同じテンプレートのエントリを指定する。さらに、クラス名とスコープ解決演算子の間に「<>」によりテンプレート型を指定する

template<class T, class T1>
T array<T, T1>::average_value(void)
{

※クラスの生成
テンプレートの定義後、<>の中に適切な型をいれて、適正な型のクラスを生成
⇒<>の中、テンプレート実引数(template argument)はクラステンプレートでは省略不可
⇒テンプレート実引数を与える⇒テンプレート特殊化(template specialization)

構文)
クラス名<型名> 識別子;

例)クラス生成
array<int, long> numbers(100);

※クラステンプレートのメンバ関数の定義

構文)
template <テンプレート引数のリスト>
戻り値の型 クラス名<型名のリスト>::関数名(引数のリスト)
{
│ …

例)

template <class T>
void Array<T>::setData(int num, T d)
{
    data[num] = d;
    ...

※配列データはクラステンプレート内で
│ T data[5];
のように宣言されているものとする

※すべてのメンバ関数の定義をクラステンプレートの中で行わない場合の注意

※~第2版C++まで
クラステンプレートを入れ子にして利用する際は、型名をとじるための>と>との間にスペースを入れて宣言する
⇒シフト演算子>>と解釈される
⇒第3版のC++では言語仕様が改定されて大丈夫になった。

※クラステンプレート定石
ヘッダファイルのみで実現
⇒~第2版C++まではexport宣言でテンプレート宣言と定義を分離しようとした
⇒完全に実装できないコンパイラ多数
⇒第3版C++ではexport宣言削除
⇒テンプレート関数、テンプレートクラスはヘッダ中に定義を埋め込み、それを利用するプログラムでインクルードする。


_◇クラステンプレートの具現化とその条件

※クラステンプレートのメンバ関数は、プログラムで呼び出されているもののみが具現化され、その他は具現化されない。
⇒呼び出さなければ生成されないという規則を逆手にとることも可能ではある。

①クラスのコンストラクト時に実引数にデフォルト値が定義されているならば、実引数の型はデフォルトコンストラクタを持たねばならない
②コピーコンストラクタを呼び出すのであれば、実引数の型もコピーコンストラクタを持たねばならない。
③代入可能なセッタを利用するならば、Type型は代入演算子によって代入可能な型でなければならない。
④関係演算子<で比較できること。(C++の標準ライブラリの多くで採用)
⇒<以外の関係演算子や等価演算子で比較可能でなくてもよい
⑤Typeは出力ストリームに対して挿入子<<で出力可能な型であること
⇒operator<<は、クラステンプレートの外の非メンバ関数として関数テンプレートで定義する。


_◇メンバテンプレート
member template

関数テンプレートとクラステンプレートは、クラスの中で定義することもできる

①通常クラス(非テンプレートクラス)内での定義

例)

class A {
    template <class T1> void f1() { ...実装 }    //関数テンプレート定義
    template <class T2> void f2(T2 t);    //関数テンプレート宣言のみ
    template <class T3> class C1 { ...実装 };    //クラステンプレート定義
    template <class T4> class C2;    //クラステンプレート宣言のみ
};

//宣言だけだったf2とC2を外部で定義(実装)
template <class T2> void A::f2(T2 t) { …実装 };
template <class T4> class A::C2 { …実装 };

②テンプレートクラス内での定義

例)

template <class T> B {
    template <class T1> void f1() { ...実装 }    //関数テンプレート定義
    template <class T2> void f2(T2 t);    //関数テンプレート宣言のみ
    template <class T3> class C1 { ...実装 };    //クラステンプレート定義
    template <class T4> class C2;    //クラステンプレート宣言のみ
};

//宣言だけだったf2とC2を外部で定義(実装)
template <class T> template <class T2> void B::f2(T2 t) { …実装 };
template <class T> template <class T4> class B::C2 { …実装 };
⇒入れ子の深さに応じてtemplate <class T> を繰り返す
⇒外側の宣言が先頭側にくる

※メンバ関数テンプレートは仮想関数にできない。


_◇非型のテンプレート仮引数

※テンプレート仮引数は型でなくてもよい
⇒その場合、以下のいずれかの型を持つ式で無ければならない。

①汎整数型または列挙型
②オブジェクトへのポインタまたは関数へのポインタ
③オブジェクトへの参照または関数への参照
④メンバへのポインタ
⇒cv修飾されてもよい


_◇typename

テンプレート内で使われる「型」についてそれが「型」であることを明示化するためのキーワード