『Effective C+』ノート

8470 ワード

一、newとdelete
1、operator new、operator deleteは、単一のオブジェクトを割り当てるのにのみ適しています.Arraysが使用するメモリはoperator new[]によって割り当てられ、operator delete[]によって返されます.
 
2、operator newがメモリ割り当ての要件を満たすことができない場合、new_が呼び出され続けます.handler関数はnew_を指すメモリの割り当てを試みます.handler関数ポインタがnullの場合、operatorは例外を放出します.
 
3、placement newとplacement delete:
Widget*pw=new Widgetの場合.
2つの関数が呼び出されます.1つはメモリを割り当てるoperator newで、1つはWidgetのdefaultコンストラクション関数です.
 
まとめ:
1、newオブジェクトの場合、通常は2つのステップが含まれます.1つはoperator newを呼び出してメモリを割り当てることです.1つは構造関数を呼び出すことです.
2、operator newの基本操作は、メモリ割り当ての要件を満たすことができない場合、new_を呼び出し続けることです.handler関数はnew_を指すメモリの割り当てを試みます.handler関数ポインタがnullの場合、operatorは例外を放出します.
 
二、構造解析賦値演算
1、C++デフォルト関数:コンストラクション関数、解析関数、コピーコンストラクション関数、付与コンストラクション関数.
 
2、自分でコンストラクション関数を宣言すると、コンパイラはdefaultコンストラクション関数を作成しません.
 
3、classがvirtual関数を含まない場合、通常はbase classとして使用されることを意図していないことを示します.
 
4、虚関数の実現?
virtual関数を実装するには、実行中にどのvirtual関数が呼び出されるかを決定するために、オブジェクトがいくつかの情報を携帯する必要があります.この情報は通常、いわゆるvptr(virtual table pointer)ポインタによって指摘される.vptrは、vtbl(virtual table)と呼ばれる関数ポインタからなる配列を指す.virtual関数を持つclassごとに対応するvtblがあります.オブジェクトがvirtual関数を呼び出すと、実際に呼び出される関数は、オブジェクトのvptrが指すvtblに依存します.
 
5、なぜ虚析构関数が必要なのか.
ベースクラスポインタが派生クラスオブジェクトを指し、現在のベースクラスの構造関数がnon-virtualである場合、派生クラスオブジェクトはベースクラスポインタを介して削除され、結果として定義されていない--実際に実行すると、通常はオブジェクトの派生部分が破棄されず、奇妙な「局所破棄」オブジェクトをもたらし、リソース漏洩を形成する.
 
6、虚析构関数はいつ必要ですか.
すべてのクラスの構造関数をvirtualとして宣言するのは、virtualとして宣言したことがないように、エラーです.(注意:虚関数テーブルはメモリを占めています.)
クラスに少なくとも1つのvirtual関数が含まれている場合にのみ、virtual構造関数が宣言されるという心得があります.(注:または、ベースクラスのみ、または虚析构関数として宣言する必要があります.)
 
7、異常を構造関数から逃がさない
解析関数に異常を吐かせないほうがいいです.解析関数が異常を吐き、プログラムが終了しない場合は、不明確な動作を招きます.異常が投げ出されると、自身の関数が終了し、関数の後続処理が実行されません.これは構造関数で発生する可能性があります.異常により、後続のデータの解放が完了せず、メモリが漏洩した.
class Test{public:  Test(){ p = new char[4]; }~Test(){throw("exception");//解析関数delete pを終了;//実行しません}private:char*p;};
 
8.virtual関数は、構築および解析中に呼び出されません.base class構築中にvirtual関数はderived classes階層に低下しません.base classコンストラクション関数の実行はderived class関数よりも早いため、base classコンストラクション関数の実行時にderived classのメンバー変数は初期化されていません.
 
9、賦値構造関数は証明とテストを必要とし、すなわち自己賦値であるかどうかを判断する.
 
三、継承された名称を隠すことを避ける
1、
class Base {

private:

	int x;

public:

	virtual void mf1() = 0;

	virtual void mf2();

	void mf3();

	...

};



class Derived: public Base {

public:

	virtual void mf1();

	void mf4();

	...

};


 
derived classのmf 4の実装部分は、次のように仮定される.
void Derived::mf4() {

	...

	mf2();

	...

}

 
コンパイラはまずlocal役割ドメイン、すなわちmf 4がカバーする役割ドメインを検索し、見つからなかった.次にclass Derivedがカバーする役割ドメインを検索しましたが、見つかりませんでした.それからbase classの役割ドメインを探して、見つけました;まだ見つからない場合は、Baseを含むnamespace役割ドメインを探して、最後にglobal役割ドメインを探します.
 
2、
class Base {

private:

	int x;

public:

	virtual void mf1() = 0;

	virtual void mf1(int);

	virtual void mf2();

	void mf3();

	void mf3(double);

	...

};



class Derived: public Base {

public:

	virtual void mf1();

	void mf3();

	void mf4();

	...

};



Derived d;

int x;

...

d.mf1();	//   ,  Derived::mf1

d.mf1(x);	//  ,  Derived::mf1   Base::mf1

d.mf2();	//   ,  Base::mf2

d.mf3();	//   ,  Derived::mf3

d.mf3(x);	//  ,  Derived::mf3   Base::mf3

 
3、2のc++が継承した名前に対するデフォルトのマスク動作を解決するには、using宣言を使用します.
class Base {

private:

	int x;

public:

	virtual void mf1() = 0;

	virtual void mf1(int);

	virtual void mf2();

	void mf3();

	void mf3(double);

	...

};



class Derived: public Base {

public:

	using Base::mf1;

	using Base::mf3;

	virtual void mf1();

	void mf3();

	void mf4();

	...

};



Derived d;

int x;

...

d.mf1();	//   ,  Derived::mf1

d.mf1(x);	//      ,  Base::mf1

d.mf2();	//   ,  Base::mf2

d.mf3();	//   ,  Derived::mf3

d.mf3(x);	//      ,  Base::mf3

 
4、base classのすべての関数を継承したくない場合があります.転送関数(forwarding function)を使用します.
class Base {

private:

	int x;

public:

	virtual void mf1() = 0;

	virtual void mf1(int);

	...

};



class Derived: public Base {

public:

	virtual void mf1() {

		Base::mf1();

	}

	...

};



Derived d;

int x;

...

d.mf1();	//   ,  Derived::mf1

d.mf1(x);	//  ,Base::mf1()    

 
 
四、純虚関数と虚関数
1、純粋な虚関数(pure virtual function)を宣言する目的はderived classesに関数インタフェースのみを継承させることである.
虚関数(impure virtual function)を宣言する目的は、derived classesに関数のインタフェースとデフォルトの実装を継承させることです.
 
2、純虚関数は実装を提供するが、抽象クラス外で実装しなければならない(もちろん派生クラスでも実装できる).
 
3、総合的に、純虚関数と虚関数の大きな違いは、純虚関数を含むクラスは抽象クラスであり、抽象クラスは対象をインスタンス化できないことである.
 
4、純虚関数(pure virtual)、虚関数(virtual)、非虚関数(non-virtual)の違いは以下の通りである.
ベースクラスのインタフェースを継承するために、またはベースクラスのデフォルトのインプリメンテーションを使用するか、またはベースクラスのインプリメンテーションバージョンを完全に使用するか.さらに簡単に言えば,ベースクラスの実装を使用するか,インタフェースのみを使用するかである.
 
五、継承されたデフォルトパラメータ値を再定義しない
#include <iostream>

using namespace std;



class Base {

public:

	virtual void mf1(int a = 1) {

		cout << "Base::mf1()" << endl;

		cout << a << endl;

	}

};



class Derived: public Base {

	virtual void mf1(int a = 2) {

		cout << "Derived::mf1()" << endl;

		cout << a << endl;

	}

};



int main() {

	Derived d;

	Base *pb = &d;



	pb->mf1();



	return 0;

}


 
 
出力:
Derived::mf1()1
 
すなわち派生クラスの関数が呼び出されますが、パラメータはベースクラスです!
virtual関数は動的にバインドされているため、デフォルトのパラメータ値は確かに静的にバインドされています.継承されたデフォルトのパラメータ値を再定義しないでください.
 
六、private継承を賢明かつ慎重に使用する
1、private継承はis-a関係ではなくimplemented-in-terms-of(あるものによって実現される).
private継承は,実装部分のみが継承されることを意味し,インタフェース部分は省略すべきである.
 
七、賢明で慎重な多重継承の使用
多重継承では、ダミーベースクラスは継承されたメンバー変数の重複を避けるために使用されます.そのため、コンパイラはvirtual継承を使用するclassesによって生成されるオブジェクトがnon-virtual継承を使用する兄弟たちよりも体積が大きく、virtual base classesのメンバー変数にアクセスする場合、non-virtual base classesにアクセスするメンバー変数よりも遅い.
 
八、inlineの内外を徹底的に理解する
1、class内に定義された関数は、通常inline関数として隠喩される.
 
2、inlineは申請で、コンパイラはそれを無視することができます.ほとんどのコンパイラは、ループや再帰などの複雑な関数inlineを拒否しますが、virtual関数の呼び出しはinlineを空にします(コンパイル期間、実行期間).
 
3、コンパイル期間は通常「関数ポインタによる呼び出し」inlineではありません.
inline void f(){};
void (*pf)() = f;
f(); //inlined
pf();//inlinedされないかもしれません
 
4、「コードを実装していないコンストラクション関数の中にはinlineの絶好の候補者のようだが、コンパイラが空白のコンストラクション関数にコンストラクションオブジェクトなどのコードを追加する可能性があるため、事実はそうではない」.