C++言語


参考資料は、
「標準講座C++」(著者:ハーバート・シルト 訳者:柏原正三)
「詳説C++」(著者:大城正典)
「プログラム言語 C++」(著者:Bjarne Stroustrup)
この他にも数冊読みましたが、この3冊を上から順に読んでいくことをお勧めします。
このページの記載に間違えがあっても、上記の本とその著者には一切関わりは有りません。

■const

タ イプ内容
const int  a = 123;
const int* p = &a;
定 数グローバル変数の場合は.rodataセクションに作られます。(x86ELF)
ロー カル変数の場合はコンパイラへの宣言で.rodataセクションに作られる訳では無いです。
初期値が無いとダメです。
void funcA(const int* pN);ポ インタ仮引数その関数内で引数の先を変更できなくなります
int funcA() const;メンバー関数関数を呼び出したオブ ジェクトの内容を変更出来なくなります。(mutable指定された変数を除く)
コンパイル時に判別されます。
普 通の関数(メンバーでは無い単体の関数。大域関数とか言ったりします)は指定出来ません。
const CA obj;オブジェクトコンストラクターを定義しないと、初 期化してないよというエラーが出ます。
constメンバー関数以外は、メンバー変数メンバー関数共にアクセスできません。
const int & b = a;
const long & c = a;
& nbsp;参照この参照を経由して値を変える事は出来ません。
実装依存ですが、定数値は アドレスを持たない為、無名オブジェクトのコピーが作られ、参照はそれを指す事が多いです。
本体がconstである場合は参照も constにしなければなりません。
参照の初期化時に型制限がゆるくなり、変換可能の型の参照なら定義できるようになります。
char* p2 =
 const_cast<char*>(p);
*p2 += 3;
キャストconstポインターの場合、constでな いポインターには代入出来ません。
指す先の値を変えたい場合は、一時的なポインターにキャストし代入して使います。


※参照は右辺値(実体を返す関数の戻り値とか)や定数値で通常は初期化出来ませんが、constにすると可能になります。

式 = 値
左辺値 = 右辺値

左辺値:変数、リファレンスで返した関数の戻り値
右辺値:定数、値で返した関数の戻り値

■volatile

最 適化時に削除されないように指定します。
(一度も使わないローカル変数をvolatile指定とかすると消されたりします)


■extern

// main.cpp ---------------------
extern int globalX;
// sub.cpp ----------------------
extern int globalX = 123;
> g++ main.cpp sub.cpp

sub.cppの方は外部リンケージ指定(ファイル外から参照 されま すよ宣言)になり、main.cppの方は外部参照宣言(ファイル外に本体が有りますよ宣言)になります。
外部リンケージは省略可能 で、付けなければ自動的に外部リンケージを持ちます。
(って言うか付ける警告が出ます。)



■static

static 付与物効果
static int x;ローカル 変数初期化は一度だけで永続的存在します。
関数限定のスコープを持ったグローバル変数。
(x86ELF の場合)初期値がなければ.localセクション、あれば.dataセクション
static char a[8];グローバル 変数宣言され たファイル外から使えないようにします。
(宣言されたファイルの範囲を、ファイルスコープや翻訳単位と呼んだりします)
(x86ELF の場合)初期値がなければ.localセクション、あれば.dataセクション
class CA{
static double d;
};
double CA::d;
メ ンバー変数 オブジェクトの数にかかわらず、クラスに1つだけの実体になります。
ク ラス内で宣言したら、グローバルな場所でクラス名とスコープ解決演算子を使って定義をしなければなりません。初期値は定義時に書きます。
(x86ELF の場合)初期値がなければ.bssセクション、あれば.dataセクション
static void funcA(){}メ ンバー関数クラス内ではstaticメンバーにしかアクセスできなくなります。
グローバ ルな変数や関数にはアクセス出来ます。
thisポインターを持たず、仮想関数にも出来ません。
cv修飾 (constとvolatile)も出来ません。
クラス名とスコープ解決演算子を使い、オブジェクト無しでコール出来ます。


■参照

● 参照の参照は、本体の参照になります。
● 参照のアドレスは本体のアドレスです。
● 参照を返す関数は、式の左側に置く事ができます。
この場合、ポインターの入った一時的なオブジェクト(無名リファレンス)を返すの でそれを経由して代入したりし ます。
●参照引数は引数を渡された時に初期化されます。渡した物のエイリアスとして動くので、コピーが生成されないので、便利です。


■名前空間

別 名付け

namespace myLib01{ /* バージョン1のライブラリ */ }
namespace myLib02{ /* バージョン2のライブラリ */ }
namespace myLib = MyLibVersion01;    // バージョン切替えして使ったりします。

編 入

namespace myLib01{ int a = 123; }
int b = 456;
namespace myLIb{
    using myLib01::a;
    using ::b;
}
myLib::a++;    // 編入先の名前空間で名前解決
myLib::b--;    // 編入先の名前空間で名前解決

合成

namespace myLib01{ extern int a; }
namespace myLib02{ extern int b; }
namespace myLib{
    using namespace myLib01;
    using namespace myLib02;
}
myLib::a++;    // myLib::myLib01::a

using命令はモジュールヘッダーに書かず、ソースコードの 方に書 くのが普通です。
インクルード先が必ずしもその名前空間を使うとは限らないから です。

名前空間の検索は通常の検索方法で見付からない場合、各実引数の型が属する
名前空間を探しに行きます。


■デフォルト実引数

● ファイルスコープで重複するとダメなので、ヘッダーの宣言部のみに書いてソースの本体には書かない形になるのが一般的です。また、モジュール化した時に ソース側に書いてヘッダーに書かないと省略機能が使えなくなるので無意味なります。

● 定数値だけでなく、式を書けますがローカル変数は含めません。

● 右の引数から設定しなければなりません。

● 呼び出し時も右から省略しなければなりません。関数の仕様拡張で引数が増えた場合に、右に引数を追加してデフォルト実引数を付けてやると古いソースで呼び 出している部分を書き換えずに済むので便利です。

● 仮想関数に渡されるデフォルト実引数は、ポインターや参照の型の方のデフォルト実引数が渡されます。
オーバーライド時は実引数も同じ ように書く事が勧められています。
仮想関数を実現する仕組みのように、デフォルト実引数も管理 データと共にどこかに記録しておけば、 動的バインディングとかも可能でしょうが、実引数がクラスや構造体立った場合など容量的にも問題で、管理データの仕組みも複雑になってしまうと思います。
(後 述しますが仮想関数は、ポインターや参照の型では無く、指し示した物の型のメンバー関数が呼ばれます)


■オーバーロード関数

● 引数の数や型が変わると違う関数として認識されます。内部では引数を名前にくっつけたりして一意の名前に作り変えてます。これをmangleと言います。 元に戻す事をdemangleと言います。
● 戻り値は関係ないので注意です。



■インライン関数

inline void funcA(){}

ヘッダーに定義ごと書かなければなりません。
必ずインライン 化されるとは限りません。って言うかされる確立の方が低いです。
[理由]:
● 再帰呼び出しをしている関数はインライン化できません。実引数や戻り値を格納する仕組みが無いからです。
● 同じ翻訳単位内に定義が無い場合も出来ません。モジュール化された物から定義を抜き出せないからです。

で、大抵 の場合普通の関数としてもコンパイルされて、インライン展開が無理な時にはその関数を呼ぶようにされます。

メン バー関数は宣言部に定義を書くと、インライン指定されたとして扱われます。



■コンストラクター

class CA{
public:
    CA();
    CA(int);
    CA(char c, int a = 123){}
};
CA::CA(){}
CA::CA(int a){}
CA funcA(){ return CA(777); }
int main(){
    CA a;
    CA a2(123);
    CA a3('A');
    CA* pa = new CA;
    CA* pa2 = new CA(456);
    CA aa[] = { CA(5), CA(6), CA('B'), CA(99) };
    CA a4 = funcA();
}

● 多重定義やデフォルト実引数が使えます。
● constやstaticは付けられません。
● 仮想関数にはなれません。(仮想関数を準備する役だから)
● コンストラクターから同クラスの別のコンストラクターを呼び出す事は出来ません。

コ ンストラクター定義が一切無い場合は、デフォルトの空コンストラクターがpublicのインラインで生成されます。(と言っても継承や仮想関数が無いとマ シン語コードには残らなかったりします)
引数付のコンストラクターだけ定義して、引数無しでオブジェク トを生成すると引数無しのコン ストラクターが無いよというエラーになります。



継承と包含による実行順序

(1) 先祖の一番古いクラスのメンバーオブジェクト(実体)のコンストラクターが、記載順に走ります。
(2) 次にその一番古いクラスのコンストラクターが走ります。
(3) 次に2番目に古いクラスに移り上記の事をして行きます。そして最新のクラスまで行きます。

この辺の仕様で各クラ ス固有のコンストラクターやデストラクターが必要になる為、コンストラクターやデストラクターは継承されません。

■コピーコンストラクター


class CA{
public:
    CA(){}
    CA(CA& a){}        //constの物と一緒に定義できますが、こちらが呼ばれるようになります。
    CA(const CA& a){}  //通常はコピー元に手を加えないのでこちらを使います。
};


●オブジェクトを使って明示的にオブジェクトを初期化する時
●関数の引数に実体のオブジェクトを渡した時
●関数の戻り値を実体で返す時
上の3つの時に生成せれるオブジェクトはコンストラクターが呼ばれず、定義してあればコピーコンストラクターが呼ばれます。

三番目の関数の実体戻り値は、実装依存らしく、GNUとHP-UXではコピーが作成されませんでした。
MSのコンパイラでは作られていました。(2004年10月確認)

//下記のコードはコンパイラの実装に依存した出力をします。
#include <iostream>
using namespace std;

class CA{
  int m;
public:
  CA(int a){ m = a; }
  CA(const CA& obj){ m = 0; }
  int get(){ return m;}
};

CA funcA(){
  CA b(123);
  return b;

}

int main (){
  CA a = funcA();
  CA a2 = a;
  return 0;
}




■初期化リスト

● staticでは無いメンバーを初期化するのに使います。
constや参照のメンバー変数は、初期化リストでしか任意の 値で初期化出 来ません。
● 直接の基底クラスの引数付きコンストラクターを呼び出す事もできます。
直接の基底ではない先祖ク ラスのコンストラクターに、初期値を渡そうとするとエラーが出ます。

class CA{
public:
    CA(int a){}
};
class CB:public CA{
    int x;
    const int y;
    int& r;
public:
    CB(int a) : CA(a), x(a * 2), y(x+5), r(x) {}
};
デバッガーで 実行すると、初期化リストの式を評価して先祖クラスを遡るので、コンストラクターの順番が新しいクラスからに見えますが(初期化リストの行を指す為)、一 番上まで遡ると順に降りて来ます。

メンバー変数は宣言順に初期化されますので、基底クラスのサブ オブジェクトの メンバーから宣言順に合わせて、初期化リスト書いておくと依存関係が見やすくなります。

初期化リストはコンスト ラクターの前に評価されるので、その結果を受けて初期化したい場合は、その後実行されるコンストラクターにその処理を書きます。



■デストラクター

class CA{
public:
    ~CA();
};
CA::~CA(){}
● 戻り値を返さない。仮引数を持たない。非公開に出来ない。の3無い関数。
● オブジェクトが無くなる直前に、必ず実行される事を言語仕様として決められています。
ローカルオブジェクトのスコープアウトや例外の スタック破棄時も必ず実行されるので、この機能に引っかけてスマートポインターとかが実装されます。

● 派生クラスのデストラクターは、基底クラスのデストラクターをオーバーライド出来ます。
その時基底クラスのデストラクターを再定義す る必要は無く、派生クラスでデストラクターにvirtual修飾子を付けるだけで済みます。

● デストラクターは仮想関数にする事が推奨されています。
理 由は、基底クラス型のポインターで派生先クラスオブジェクトを指している時に、そのポインターをdeleteした場合、仮想関数でない場合はポインターの 型である基底クラスのデストラクターが呼ばれます。そして前述のコンストラクターの実行順の逆、というデストラクターの実行順の法則だと派生クラスのデス トラクターは呼ばれません。

仮想関数にした場合は、基底クラスでも定義を書かなければなり ません。しないと呼び出す関数の実体が無い状態になり、リンクエラーになります。

[雑感]:
推測ですが、この仕様の回避作は複雑になりそうなの で、「仮想にして」というお願いな気がします。
余談ですが、仮想関数テーブルのメモリ分を許容出来るシステム なら、迷わず仮想にする べきと言われいますが、組み込み等でギリギリまで削るような場合以外は余り無いケースだと思います。また、そのケースだった場合は、C++を言語に選択す るのはちょっと辛い気がします。


継承と包含による実行順序

上 述のコンストラクターの逆順で行われます。




■ネストクラス(入れ子クラス)

class CA{
    class CInPri{
        int a;
    };
public:
    class CInPub{
        int x;
    };
};

クラス内でクラスを定義すると、ネストクラスという物になります。
スコープ上、内包するクラスの専属クラスに使われます。
publicなネストクラスの場合、スコープ解決すればクラス外でオブジェクトの生成も可能です。



■キャスト(型変換)

static_cast

コンパイル時に型変換がC++の基準に沿った型変換だった場合、キャストされます。

const_cast

コンパイル時にconstとvoletileを外すのに使います。
const_castした物自体に、代入は出来ませんので、constのポインターを普通のポインターにコピーして使ったりします。ポインターや参照以外は普通にコピー出来ます。

reinterpret_cast

ほぼ無条件でキャストされます。(constとかはダメです)
C++でも使える従来のC言語でのキャストと同等だと思います。
名前が長いのは使わないようにという配慮な気がします。

dynamic_cast

ポリモーフィッククラス(仮想関数を含むクラス)の、ポインター及び参照の型変換にのみ使えます。
void*に変換すると最派生のポインターを返します。実行時にエラーチェック付でキャストします。

変換する時の情報をメモリ上に持っていて仮想関数テーブルの[-1]にアドレス格納するのが、よくある実装らしいです。
この情報をRTTI(Run Time Type Information)と呼びます。

typeid演算子は、<typeinfo>で宣言されているtype_infoクラスのconstオブジェクトへの参照を返します。
bool type_info::operator==(const type_info & rhs) const;
bool type_info::operator!=(const type_info & rhs) const;
bool type_info::before(const type_info & rhs) const;
const char* type_info::name() const;

dynamic_castやtypeinfoはプログラムの柔軟性を無くす事が多いので、極力使わない事をお勧めします。
クロスキャスト、ダウンキャストなどと呼ぶ事があるキャストに使われますが、この時点で設計が破綻している場合が多いので、他人のソースを引き継いで短期間で直すといった時以外は使わない事をお勧めします。

全てのクラスがポリモーフィッククラスと仮想関数なJavaと違って、仮想関数指定をしてそれを含んだ物しかポリモーフィッククラスにしないのは、C言語の構造体との互換性の為です。


#include <iostream>
#include <typeinfo>
using namespace std;

class CA {};
class CB:public CA{virtual void funcB(){}};
class CX {virtual void funcX(){}};

class CC:public CB, public CX, public CA{};//CAはCBの中と直接と二つ入れています。

int main(){
  int a= 60;
  int *ip = &a;
  char c = a;
  cout << *ip  << endl;
  cout << (char)(*ip)++  << endl;
  cout << char(*ip)  << endl;
  // ----------------------------
  CC *p = new CC();
  CC obj;
  //  CB obj2(dynamic_cast<CB>(obj));//実体なのでエラー
  cout << &p << endl;
  CX *xp = dynamic_cast<CX*>(p);
  CB *bp = dynamic_cast<CB*>(xp);
  cout << "///" << endl;
  if(typeid(obj) == typeid(*p) ){
    cout <<   typeid(obj).name() << endl;
  }
  return 0;
}




■グローバルコンストラ クー、デストラクター

グ ローバルなオブジェクトや、staticなオブジェクトは、main関数の走る前にコンストラクターが走り
デ ストラクターはメイン関数をリターンしたの後にデストラクターが実行されます。





■メンバー型、メンバー 列挙定数(typedefとenum)

class CA{
private:
    typedef int NUMBER;
    enum KENMEI { hokkaido=0, aomori, miyagi };
    NUMBER num;
    KENMEI ken;
};
な 感じで。主にクラス内部専用の型や定数定義に使います。
またpublicにしてメンバー関数の引数に指定させるとかし ます。わかりず らいかなぁ…



■メンバーへのポインター型

class CA{
public:
    int a;
    void funcA(int x){}
};

int CA::* pm =  &CA::a;    //&(CA::a)だとダメです

typedef void(CA::* PMF)(int);
PMF pmf = &CA::funcA;


int main(){
  CA obj;
  obj.*pm = 123;
  ((&obj)->*pmf)(456);
  return 0;
}

オブジェクトアドレスの先頭から各メンバーへのオフセットアドレスを、取得します。
関数ポインターをメンバー関数でも使いたいというニーズには応えますが、あまり使わない事をお勧めします。



■フレンド

フレン ド関数

class CA{
    int a;
public:
    friend void funcF(CA & obj);
};
void CAFried00(CA & obj){
    obj.a++;
}

メンバー関数では無いので、メンバー名を直接参照したり thisポイン ターを使ったりは出来ません。
ので、操作対象を引数でもらいます。もらわないと無意味なフレ ンドになってしまいます。
継 承もされません。

オブジェクト指向的には手段としてフレンド関数は使わない方が 良いとされてます。
オ ペレーターのオーバーロード以外には使わない方が良いと思います。

フレンドクラス

class CA{
    int x;
    friend class CB;
};
class CB{
    int a;
public:
    void funcA(CA& obj);
    void funcB(CA& obj){}
};
void CB::funcA(CA& obj){
    obj.x = 123;
}

クラスの関数が全てフレンドという場合、クラスごとフレンドに してしまうという方法です。
   


■継承

class CBase{};
class CDerive : public CBase {};

派 生クラスのオブジェクトは、基底クラスのオブジェクトでもあります。(is-aの関係)
コンストラクターとデストラクターは継承され ません。
継承先に同じ名前のメンバー変数やメンバー関数(引数構成が同じの場合)が有った場合、継承先のスコー プが優先されます。基底側へのアクセスは、基底クラス名::変 数名でアクセス出来ます。

継承先に同じ名前のメンバー関数が有り、引数の構成が違った場 合(オーバーロードしようとした場合)基底側 の関数が隠蔽されます。間違って呼ぶとエラーが出ます。

class CBase{ void funcA(char c){} };
class CDerive : public CBase{ void funcA(char* pc){} };
CDerive d;
char c = 'C';
d.funcA(&c);        //ok
d.funcA(c);         //エラー
d.CBase::funcA(c);  //ok

これは先祖のクラスの詳細が解らず、実引数を間違えてもエラー が出て解るようにとの考慮らしく、
先 祖の同名で引数構成の違う関数を使う時はスコープ解決演算子で指定して使わなければなりません。


publicな 継承の場合、派生クラスオブジェクトを、基底 クラスオブジェクトとして使用できます。その逆は出来ません。(public以外の場合は基底クラス型のポインター経由でサブオブジェクトに触れてしまうのでダメ)

派生クラスオブジェクトを、基底クラスオブジェクトに代入出来 ます。そ の逆は出来ません。
(↑の時はサブオブジェクトとして持っている基底クラスオブ ジェクトを切り出します。後述の仮想関数解決用のシス テムが用意するポインターも切り取られてしまいます)

派生クラスオブジェクトのアドレスを、基底クラス型のポイン ターへ代入する事が 出来ます。その逆は出来ません。
(↑の時はサブオブジェクトとして持っている基底クラスオブ ジェクトのアドレスが抽出されます)
void funcA(CBase* obj);
CDerive d;
funcA(&d);
参 照も同じです。
void funcA(CBase & obj);
CDerive d;
funcA(d);

派 生クラスオブジェクトを、基底クラスオブジェクトに代入は極力避けた方良いです。
派生クラスで実装した物を全てスライシングではぎ 取ってしまうので、意味の無い継承になったりしてしまいます。
出来る限り、派生クラスオブジェクトへのポインターを基底クラ スのポイ ンターに変換するか、
基底クラスへの参照を派生クラスオブジェクトで初期化するとか した方が良いです。


継 承アクセス修飾子

(省 略された場合、structとunionはpublic、classはprivate)
public
(変 化無し)
継承前継承 後
privateprivate
protectedprotected
publicpublic

private
(全 部private)
継承前継 承後
privateprivate
protected
private
publicprivate

protected
(public だけprotectedに変化)
継承前継 承後
privateprivate
protectedprotected
publicprotected

ア クセス修飾子
privateそ のクラスのメンバーとフレンド関数しか触れません。
基底側でprivateだった物は、継承先で追加されたメンバー関数やフレンド関 数では触れないです。
public誰 でも触れます。
protectedそ のクラスのメンバーとフレンド関数しか触れません。
基底側でprotectedだった物は、継承先で追加されたメンバー関数やフレン ド関数では触れます。


※ 継承先で追加されたメンバー関数やフレンド関数の中では、privateやprotectedな継承をしたクラスのオブジェクトを基底側に変換できます。
(そ れ以外の場所ではこの変換は出来ません。っていうか使わない方が良いと思います。理由は後述のis-a関係を破綻させてしまうからです。)



多重継 承(multiple inheritance略してMI)

class CBase{};
class CBase2{};
class CBase3{};
class CDerive1 : public CBase, public CBase2 {};
class CDerive2 : public CBase, private CBase2, public CBase3{};

クラス名の後にコロンで区切って、継承アクセス修飾子とクラス名を書き、カンマで区切ります。
このコロンより右の羅列を、基底指定子リストといいます。
基底サブオブジェクトのコンストラクターの実行順は、このリストで指定した順に行われます。
デストラクターはその逆順です。


● publicな両親
複数の概念の合成や、インターフェイスを複数付けたりする時に使います。
クラスを肥大化させ過ぎたり、カプセル化を弱めすぎないように注意が必要です。

● publicとprivateな両親
インターフェイスと実装を継承したりします。
両親間の関係は、ほとんど無い場合とある程度関係がある場合があります。
無い場合は、片方が低レベルな処理を担当して、もう一方が高レベルな処理を担当するケースが多いです。
ある場合は、片方がインターフェイスで、もう一方が実装を主に担当しているケースが多いです。


● privateな両親
複数のクラスから実装を得る時にしようしますが、包含を使う方が利点が多いので通常は包含を使います。


多重継承の曖昧性(ambiguous)

class CA{ public: int x }; class CB{ private: int x };
class CC:public CA, public CB{ public: int x; };
CC obj;
// obj.x = 123;    // エラー
obj.CA::x = 123;
obj.CB::x = 456;

● 両親に同名のメンバーが居た場合、アクセス修飾で一方しかアクセス出来ない状態でも、スコープ解決しないでアクセスするとエラーになります。
スコープ解決すれば回避出来る問題ですが、どのクラスか指定してしまう為仮想関数の動的なバインディングは使えません。

● 上記の回避作の常套手段だそうです。



工 事中








仮想継承(virtual inheritance 略してviとは言わないっぽい。viがメジャーだからかなぁ)

class CA{ public:int a;};
class CB:public virtual CA{};
class CC:public virtual CA{};
class CD:public CB, public CC{};

● 同一の先祖を持った両親を持つと、オブジェクトの中に二つのご先祖サブオブジェクトが出来てしまいます。virtualで継承(仮想継承)すると、基底側を仮想基底と呼び、仮想基底のサブオブジェクトが複数仮想基底クラスによって共有されます。

CD obj;
if( &(obj.CC::a) == &(obj.CB::a) ){
    cout << "両親が仮想継承なのでアドレスが同じ。";
}else{
    cout << "両親のどれかもしくは両方が仮想継承では無いのでアドレスが違う(別のサブオブジェクト)";
}
cout << endl;


仮想基底クラスのコンストラクター

● 仮想基底クラスのコンストラクターは、特別ルールでどの仮想基底で無いクラスのコンストラクターよりも先に呼ばれます。
● 複数の仮想基底クラスが有った場合は、その中から通常のコンストラクターの順番ルールで順番を決めます。
● デストラクターはその逆になります。

多重継承のコンストラクターの実行順序

(1) 基底指定子リストの優先順にしたがって行き付く所まで遡ったら、そのクラスのコンストラクターを呼びます。
(2) 次に来た道を、まだ行った事の無い分岐にたどり着くまで戻りながらコンストラクターを呼びます。
(3) 分岐に戻ったら、分岐点になるクラスのコンストラクターは呼ばず、分岐の行って無い方の行き止まりまで行ってコンストラクターを呼び再び戻りながらコンストラクターを呼びます。
上記の繰り返しが、通常の場合です。

class CV1{};
class CV2{};
class CV3:public  CV1, public  CV2{};
class CA{};
class CB:public CA, public  CV1{};
class CC:public CB,  CV3{};
CC obj;
// CA(), CV1(), CB(), CV2(), CV3(), CC() の順番で実行される



仮想基底を使った場合は、通常のルールに「仮想基底は優先特別ルール」を加えて実行します。

class CV1{};
class CV2{};
class CV3:public virtual CV1, public virtual CV2{};
class CA{};
class CB:public CA, public virtual CV1{};
class CC:public CB, virtual CV3{};
CC obj;
// CV1(), CV2(), CV3(), CA(), CB(), CC() の順番で実行される

仮 想基底は、通常のコンストラクターの連鎖とは無関係で起こるので、特例として直系の子孫でない派生クラス初期化リストで初期化できます。ただし仮想基底サ ブオブジェクトの初期化は最終派生クラスの初期化リストで一度だけ初期化され、他の初期化リストに記載されていても無視されます。



■仮想関数

class CBase{
public:
    virtual void funcA(int a){}
    virtual int funcB(){ return 0; }
};
class CDerive : public CBase {
public:
    void funcA(int a){}
};
int main(){
    CDerive d;
    CBase* pb = &d;
    pb->funcA(123);    //CDeriveのfuncA()が呼ばれます。
    CBase& rb = d;
    rb.funcA(123);     //CDeriveのfuncA()が呼ばれます。
    CBase b = d;
    b.funcA(123);      //ダメ。CBaseのfuncA()が呼ばれます。
    pb = &b; pb->funcA(123); //これもダメ。
}
● ポインター経由で 仮想関数が呼ばれた時に、ポインターの先にいるオブジェクトの方の関数が呼ばれます。参照でもいけます。
● 派生先で独自の定義がされな かった場合は、基底の関数が呼ばれます。(メンバー関数が継承されるのと同じ)
● 仮引数の構成やconst指定も一致していないとダメ です。
戻り値の型も一致しないといけないのですが、派生側から基底側 へのポインターか参照の適切な変換で、cv指定が同じか強くなる 方向の変換ならば特別に許されます。
(自動変換がかかり変換前の型で渡されますが、実体以外なので 大丈夫)

■実装例(機種依存)

一般的な実装は、各クラスごとに 仮想関数のポインターをテーブルで確保し、各オブジェクトにそのテーブルへのポインターをこっそり持たせます。
コンストラクターのタ イミングに引っかけて、テーブルへのポインターにテーブルのアドレスを設定します。
継承でテーブルは引き継ぎますが、上書き(オー バーライド)されれば、そのテーブルのアドレスも新しい関数へのポインターに上書きされます。
テーブルには仮想関数が宣言順に並んで たりするので、派生先で宣言の順番を変えると実装によっては困った事が起きる可能性があります。
pb->funcA(123);    // (pb->テーブルへのポインター[0])(this, 123)
pb->funcB();       // (pb->テーブルへのポインター[1])(this)

メ モリマップイメージ例
基 底クラスオブジェクト
テーブルへのポインター
派 生クラスオブジェクト
実装依存ですので void*とかのキャストで無茶をやっても保証されません。
まあ、出来るとは思いますが…

派 生クラスを更に派生させ親子関係を先祖関係みたいにする事を多段継承と言うらしいです。
多段継承で仮想関数を呼んだ場合、その先のオ ブジェクトが仮想関数を上書きしていない場合、
先祖を辿っていき最初に見付かった仮想関数を呼びます。まあ実 装的にそうなると楽か と…
(最後に上書きをした仮想関数で、ファイナルオーバーライダー というらしいです)
多重継承をした時に、同一の先祖クラスの仮想関数を両親クラスの両方でオーバーライドし、派生クラスでオーバーライドしないと、複数のファイナルオーバーライダーは禁止という規則に従ってエラーになります。
(このエラーは検出は「曖昧性の検出はアクセスした時」というルールに沿わず、記述しただけでコンパイルエラーです)

■純粋仮想関数

class CA{
public:
    virtual int funcA(char*, int) = 0;
};

● 仮想関数のプロトタイプ宣言の後ろに、"=0"を付けるとその関数は純粋仮想関数というものになります。
● 純粋仮想関数は、デフォルトの定義が有っても無くても構いません。(書かない方が推奨されています)
● この宣言をメンバーに持ったクラスは、オブジェクトを生成出来ない抽象クラスになります。
● 先祖を辿ってオーバーライドしてない純粋仮想が有った場合、そのクラスも抽象クラスとして扱われます。
● 抽象クラスは、仮引数や戻り値の型に指定できません。

クラス設計上で継承して使う為のクラスは、実体を作らない方が 良い物がでて来ます。その時に純粋仮想関数を使ってクラスを、抽象クラス(アブストラクトクラス)にする事が推奨されています。
一般的に派生の末端のクラス以外は、ほとんど抽象クラスになる と言われています。

■純粋仮想デストラクター

● 仮想関数はを持って無いクラスを、抽象クラスにしたい時に使います。
● デフォルトの定義を書かないとリンクエ ラーになります。(デストラクターの呼び出し法則により、基底クラスの物も呼び出されてしまう為です)



■オペレーター

■多重定義可能な演算子
new delete new[] delete[] + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || , ->* = () [] -> ++ --

■多重定義不可能な演算子

. :: .* ?

■friend関数で多重定義可能な演算子

= () [] ->

●代入演算子=は継承されません。
●staticメンバー関数では定義できません。
●クラス型か列挙型の実体か参照の仮引数を一個以上必要とします。
●2項演算子定義で、メンバー関数では引数が1つになり、演算子の左側がそのオブジェクトそのものになり、右側が引数に指定したオブジェクトになります。
●非メンバー関数で2項演算子を定義する時は左と右の両方の為の引数が2つ要ります。

friendを使う利点は "組み込み型(intとか)"+"ユーザー定義クラス"の時です。通常はメンバー関数を使います。


#include <iostream>
using namespace std;

class CA{
  int a;
  int arr[10];
  double dou;
public:
  CA(int x):a(x){cout << "mk--" << a << endl;}
  CA operator+(const CA& rhs)const{    //最後のconstはメンバーに手を加えない宣言
    return CA(a+rhs.a);//temp;
  }
  friend CA operator-(const CA& lhs, const CA& rhs){
    //const にすると右辺値(実体を返す関数の戻り値とか)で初期化できる
    return CA(lhs.a-rhs.a);
  }
  int get(){ return a; }
  virtual ~CA(){
    cout << "rm--" << a  << endl;
  }
  //前置++(一つ増加させた値を返す)
  CA operator++(){
    a++;
    return *this;
  }
  ///  friend CA operator--(
  //後置++(一つ増加させる前の値を返す)
  CA operator++(int notused){
    CA temp = *this;
    a++;//コメントアウトしてもdouble notusedというのは無い。
    dou = 0.123;
    return temp;
  }
  friend CA operator--(CA& lhs, int notused){
    CA temp = lhs;
    lhs.a--;
    return temp;
  }
  CA operator[](int i){
    return arr[i];
  }
  char* operator()(int x){
    return "abc";
  }
};

CA funcA(){// return CA(999); }
  CA a(888);
  return a;
}

int main(){
  cout << "=========" << endl;
  CA a(12);CA b(3);
  cout << a.get() << endl;
  cout << b.get() << endl;
  CA c = a+b;
  cout << c.get() << endl;
  cout << "---" << endl;
  CA temp = funcA();
  temp.get();
  cout << a(1) << "<-" << endl;
  cout << "---" << endl;
  a--;
  return 0;
}



■型変換オペレーター

#include <iostream>
using namespace std;

class CB{};
class CA{
  int a;
  CB b;
public:
  CA(const int& x):a(x*2), b(0) {}    //intから変換
  explicit CA(double d):b('a'){}      //=で初期化出来ないように指定
  int get(){ return a; }
  operator int() const { return a; }  //intへ変換
  operator CB() const { return b; }   //CBへ変換
};

void funcA(CA obj){
  obj.get();
}

int main(){
  // -------------------------
  // 自クラスへの変換 --------
  int a = 123;
  CA objA(a);                    //通常のコンストラクター
  CA objA2 = a;                  //intからの変換
  funcA(777);                    //intからの変換
  objA = (CA)a;                  //C言語のキャスト
  objA = CA(a);                  //C言語のキャスト
  // -------------------------
  // 他クラスへの変換 --------
  int b = objA;                  //intへの変換
  CB objB = objA;                //CB型への変換(CBとCAに継承関係は無し)
  return 0;
}
/*
publicな基底クラスを持つ、兄弟クラスのコンストラクターの引数に、
親クラスを指定すると兄弟同士を=で型変換して代入してしまう(スライシング)ので注意です。
class CA{}
class CB:public CA{}
class CC:public CA{}
CB b;
CC c = b;
*/

■=(イコール)オペレーター

デフォルトの=オペレーターはビットコピーと言われるメンバーをそのまま複写します。
派生クラスであればデフォルトの=オペレーターは基底の=オペレーターを呼びます。
(何層も継承関係が有った場合、更に親の=を呼ぶのでサブオブジェクト全てが複写されます)

#include <iostream>
using namespace std;
class CA{
  int n1, n2;
public:
  CA(int a, int b):n1(a), n2(b){}
  CA& operator=(const CA& obj){
    n1 = obj.n1;
    n2 = obj.n2;
    return *this;
  }
  int get1(){return n1;}
  int get2(){return n2;}
};

class CB:public CA{
  char c;
public:
  CB(int a, int b, char c='A'):CA(a, b), c(c){}
  CB& operator=(const CB& obj){
    if( this == &obj) return *this;
    CA::operator=(obj); //デフォルトのオペレーターには実装されています。
    return *this;
  }
  char getc(){ return c; }
};

class CC:public CB{
  double d;
public:
  CC(int a, int b, char c=0, double d=0.5):CB(a, b), d(d){}
  double getd(){return d;}
};


int main(){
  CB bb(123,456);
  CB bc(333, 555);
  bb = bc;
  CC cc(5, 6);
  CC cd(9, 10);
  cc = cd;
  cout << bb.get1() << ":" << bb.get2() << bb.getc() << endl;
  cout << bc.get1() << ":" << bc.get2() << bc.getc() <<  endl;
  return 0;
}

■new,deleteオペレーター

newとdeleteをオーバーロードする時に、コンストラクターやデストラクターは内部で呼ばなくても、適切なタイミング呼んでくれます。
内部でC++に特化した関数は呼ばない方が良いでしょう。(関数内でnewを使っている可能性があるので、再帰的呼び出してスタックオーバーフローしてしまうかも知れません)
コンストラクターとデストラクターの関係に似ていますが、newはsize_t引数の他にも引数を持たせる事が出来ます。

void* operator new(size_t sise);
void* operator new[](size_t size);
void operator delete(void* p);
void operator delete[](void* p);




■テンプレート

export template<class T> void funcA(T a){}        //ヘッダー以外にも書けます。
// 本来マクロの高級版のような仕組みなのでヘッダーにしか書けなかったのですが。

■テンプレート関数

// 基本
template<class T> void funcA(T a){}
int a;
funcA(a);

// 引数無し
template<class T> T funcA(){ T a=0; return a; }
int a;
a = funcA<int>();

// デフォルト実引数付き
template<class T> void funcB(T a=123){}
funcB<int>();

//デフォルト 実引数付きで
template<class T, class T2> void funcC(T a=123, T2 b=456){
funcC(777, 888);        //普通に呼び出し(整数定数だとint扱い…なはず)
funcC<int,int>();       //引数を二つ省略
funcC<int,int>(1);      //引数を一つ省略

■テンプレートクラス

template <class T, class T2> class CA{
public:
    T funcA(T2);
};
template <class T, class T2> T CA<T, T2>::funcA(T2 a){
    return static_cast<T>(a);
}
CA<int, long> obj;

● メンバーなテンプレート関数は仮想関数にはなれません。
仮想関数テーブルはコンパイル時に仮想関数の数でサイズが決められます。テンプレートはコンパイル時に毎回変わるので、毎回仮想関数テーブルを作り直していたらモジュール化出来ないと言うのが理由だと思います。
● デストラクターはテンプレート関数になれません。
デストラクターは一意性が有るため、複数のパターンを作る意味がないからだそうです。
コンストラクターはテンプレート化出来ます。


■テンプレート・テンプレート仮引数

template <template <class T> class T2> class CA{
    T2<int> obj;
};
template <class T> class CB{};
CA<CB> a;

クラスCA<CB>に、CB<int>型のメンバーを持たせています。


■テンプレート非型仮引数

template < class T, int i> class CC{
  T arr[i];
};
CC<int, 10> c;

テンプレートに値を渡しています。
渡せる物は以下の物になります。
仮引数の型実引数の値
整数型定数式
列挙型定数式
関数外部リンケージを持つ関数ポインター、または参照
仮引数にはCV修飾が可能で、実引数は仮引数よりCVが弱ければよい
オブジェクト外部リンケージを持つオブジェクトのポインター、または参照
仮引数にはCV修飾が可能で、実引数は仮引数よりCVが弱ければよい
※ポインターを渡す時は、&演算子を使うか配列名か関数名を直接渡さなければならいという制限があります。


template < const char* str> class CS{};

//C++の文字列リテラルは外部リンケージを持たない為エラーになります
//CS<"abcdef"> s;    


//C++ではconstを付けると外部リンケージを持たず、内部リンケージを持つ為渡せません
//const char arr[] = "abcdef";

//&演算子を使うか配列名か関数名を直接渡さなければならいのでエラーになります
//char * str = "abcdef";
   
char arr[] = "abcdef";
CS<arr> s;

■デフォルトテンプレート実引数

template < class T = int , int i = 5> class CA{
  T arr[i];
};
CA<> obj;

● テンプレート仮引数にデフォルト実引数を渡せるのは、テンプレートクラスだけで、テンプレート関数では渡す事は出来ません。
● 通常のデフォルト実引数と同様に右から省略可能です。


■非テンプレート関数との名前競合

void funcA(int a){}
tmeplate <class T> void funcA(T t){}
funcA(123);    //前者を呼び出す
funcA<>(123);    //後者が呼び出される

通常の関数で隠蔽されます。<>を付けて明示する事により呼び出せます。


■型指定

int x;
template<class T> void funcA(T t){
    typename T::X * x;
};

T::Xが型であることを明示しています。typenameが無かった場合、T::Xがメンバー変数だった場合はかけ算になってしまいます。


■メンバーテンプレートのtemplte指定


class CA{
public:
    template<int> void funcA(){}
    template<int> static void funcB(){}
};
template<class T> void funcB(T t){
    t.template funcA<123>();        //t.funcA<123>();だとダメ。
    (&t)->template funcA<456>();    //(&t)->funcA<456>();だとダメ。
    T::template funcB<333>();        //T::funcB<333>();だとダメ。
}


■明示的特殊化

template<class T> class CA{
public:
    T a;
};
template<> class CA<const char*>{    //char*型が渡された時はこっちを使って宣言
public:
    char a[16];
};
   
template<class T> bool funcA(T t1, T t2){
    return t1 < t2;
}
template<> bool funcA(const char* p1, const char* p2){    //char*型が渡された時はこっちを使って宣言
    return strcmp(p1, 2) < 0;
}

明示的に特殊なバージョン(intバージョンとかchar*バージョンとか)を作るには、通常のバージョンを作らなければなりません。


template<class T> class CA{
    T t;
public:
    CA(T t):T(t){}
};

template<class T> class CA<T*>{    //ポインター型だったらこっちを使って宣言
    T *p;
public:
    CA(T *pt):p(pt){}
};



工 事中












■STL

工事 中


■ 例外

class CErr{};
void funcA(bool b){
  if(b == false){ throw CErr(); }
}

int main(){
  try{
    funcA(false);
  }catch(int x){
    //int型の例外を受け取る
  }catch(...){
    //全ての例外を受け取る
  }
  return 0;
}

/*

●自動変換は行われず、同じ型で厳密にキャッチされますが、cv指定はキャッチする側が強くなれば許されます。
●void*型は全てのポインター型と一致します。
●派生型は実体、ポインター、参照ともに公開基底型としてもマッチしますので、キャッチ順を前に持って
こなければなりません。

■投げ方

例外はオペランドのコピーの一時オブジェクトを作り、それを受け取り側に渡すようにしています。
throwはスタックフレームを巻き戻すために、関数内で作った物(ヒープ以外)では関数終了後に開放されてしまうので、コピーを作って投げる事になります。
例外は値で投げるのが良いとされているのは、ポインターで投げるとその先を開放してよいかを受け取りが判らないからです。
上記のようにコンストラクターで一時オブジェクトを作って投げるのは定番です。

■受け取り方

受け取りには参照を使うのが望ましいです。値で受け取ると更にコピーが作られてしまい、基底型で派生を値で受け渡しすると、派生で独自に追加した部分のオブジェクトが切り取られてしまうからです。

例外は呼び出し側を遡り、誰もキャッチしなかった場合、void terminate(void)関数が呼び出されます。
(terminate_handler型として<exception>で定義されています)
terminateはデフォルトでabortを呼び出します。
set_terminate(terminate_handler f)関数で自作関数を呼び出すように指定できます。

catch処理の中で、throwの後の評価式を省略した場合、受け取った例外をそのまま投げます。
何も受け取っていないのに例外の再スローをしようとした時もterminate関数が呼ばれます。


■制限

関数名の後に投げ事のできる例外の種類を指定することが出来ます。
void funcA() throw(int, char){}//intとcharを投げることが出来ます。
このとき関数がメンバーに変更を加えないという指定のconstより後ろに書きます。
指定外の例外を投げるとbad_exception関数が呼ばれます。この関数はデフォルトでterminateを呼びます。
この関数もset_unexcepted関数で自作関数を登録出来ます。

■標準例外

<exception>に定義されている投げる用の標準exceptionクラスには下記のメンバーが定義されています。
exception::exception() throw();
exception::exception(const& exception) throw();
exception& exception::operator=(const exception&) throw();
virtual exception::~exception() throw();
virtual const cahr* exception::what() const throw();

what関数で例外の情報を文字列で渡したりします。


*/


■設計概念的なもの

イ ンターフェイスと実装

■ イ ンターフェイス

● 公開メンバー関数とその使い方が、インターフェイスになります。
● privateと protectedな継承では、非公開になるので実装に変わります。
● publicだった場合はインターフェイスとして継承されます。

純粋仮想関数の宣言のみで構成し、継承して使ってもらうクラス の事をインターフェイスクラス、または抽象基底クラス(アブストラクト・ベース・クラス)と呼びます。
一般的にはインターフェイスクラスは空の定義の仮想デストラク ターを持ちます。

■実 装

● 定義部分が実装にあたります。


■関係

■is -a関係

D is a B.
● 冠詞 a が付いている事で、一種のという意味になります。
● B is a Dは成り立ちません。(イコールでは無い)
● public継承されたクラスと基底クラスの関係になる。
● Bに対して成立する条件は、Dに対 しても必ず成立します。
● DをBとして扱えると言う事になります。
● DはBをカスタマイズした物という事にもなりま す。
● Javaにはpublicな継承しかありません。

is-a関係のメンバー関数
関 数の種類機能継承物
非 仮想関数すべての派生クラスで共通インターフェイスと実装
非 純粋仮想関数派生クラス独自の機能かデフォルトの機能インター フェイスとデフォルトの実装
純粋仮想関数派 生クラス独自の機能インターフェイス

クラス設計上の指針として、
● 仮想関数以外はオーバーライドしない。
● オーバーライドしない関数をvirtualにしない。
● 純粋仮想関数にデフォルトの定義を持たせない。
事が推奨されています。

UMLでは△に実線を付けた矢印で、実装 を含む継承を表し、△に破線を付けた矢印でインターフェイスの継承を表しています。
矢印の方向は、派生方向の逆(子から親を指しま す)です。

■has-a関係

A has a B.
● AはBを持っているという意味です。
● AをBの様に扱うことは出来ません。
● オブジェクトの実体か、ポインターか参照をメンバー変数に する事で関係が成立します。
● メンバー変数に持つ事を包含といいます。
● is-a関係の継承のように、ポリモフィズム的に使えませんが、継承の弱点である依存クラスの仕様変更による影響の大きさが、最小限で済むという利点があ ります。

UMLでは普通の矢印(↑)の始点に◇を付けた実線で表しま す。(集約やアグリゲーションと呼ばれます)
矢印 の方向は、持っている方から持たれている方に向いてます。(◇が付いてる方が持ち主です)
上記の継承の矢印の向きもそうですが、メン バーにしろサブオブジェクトにしろ「これ内包しています」という意味合いで取ると良いかも知れません。
ちなみにUMLのもう一つの主 要な線に、関連という物が有り、普通の矢印(↑)で表します。(実線だけで矢印を使わないケースも多いのですが、一方的な関係だった場合矢印にします)
こ れは、オブジェクトは持たないが生成して返す(Creates)や、メンバーとしては持たないが一時的に仮引数やローカル変数で持って使う(Uses)な どがあります。


■is -implemented-using関係

A is implemented using B.
● AはBを使っ て実装されている。
● AとBは普遍的な直接関係は無く、必ずBじゃなくてもAは大丈夫と言う関係です。
● 包含か、 privateかprotectedな派生を使います。

基本的に包含を使いますが、基底クラスの protectedメンバーにアクセスしなければならない時は継承を使います。
privateとprotectedな派生がC++に あるのは、クラスの実装上に基底クラスのprotectedメンバーにアクセスする必要が有る時の解決策の為とも言われています。




■ その他

■リンケージ

extern "C" void funcA();
extern "C" {
    void funcB(){}
    int funcC(char* c){ return 0; }
}

■インラインアセンブラ

asm( /* assembler code */ );
asm{ /*
      *
assembler code
      *                */ }

■Cの関数だけど(備忘録)

  char *p="ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  int n = 12;
  printf("%.*s です。\n", n, p);
> ABCDEFGHIJKL です。


■前 方宣言(前方参照)

class CB;
class CA{
public:
    void funcA(CB b);
};
class CB{};
void CA::funcA(CB b){}


戻 る