Effective C++読書ノート8

5045 ワード

条項28:handlesがオブジェクトの内部成分を指すことを避ける
class Point{
	
public:
	Point(int x, int y);
	//...
	void setX(int newVal);
	void setY(int newVal);
	//...
};

struct RectData{
	Point ulhc;
	Point lrhc;
};

class Rectangle{
	//...
private:
	std::shared_ptr<RectData> pData;
};
class Rectangle{
public:
	//...
	Point& upperLeft() const { return pData->ulhc;}
	Point& lowerRight() const {return pData->lrhc;}
};

このような設計はコンパイルすることができるが,誤りであり,矛盾している.両方の関数はconstとして宣言され、Rectangleは変更されないことを示します.一方、両方の関数はreferenceがprivateデータを指すことを返し、呼び出し者はreferenceで内部データを変更することができます!
これは、まず、メンバー変数のカプセル化性がreferenceを返す関数のアクセスレベルに最も等しいことを教訓としています.この例ではulhcおよびlrhcはprivateとして宣言され、実際にはpublicである.
第二に、constメンバー関数がreferenceを送信し、後者が指すオブジェクトが自身に関連付けられ、オブジェクトの外に格納されている場合、この関数の呼び出し者はそのデータを変更することができる.
これらの関数で遭遇した2つの問題は,彼らの戻りタイプにconstを加えるだけで簡単に除去できる.それでもupperleftとlowerRightは「オブジェクトの内部を表す」handlesを返し、dangling handles:つまりこのhandlesが指すオブジェクトが存在しない可能性があります.このような存在しないオブジェクトの最も一般的なソースは、関数の戻り値です.たとえば、次のようになります.
class GUIObject{};
const Rectangle boundingBox(const GUIObject& obj);

GUIObject* pgo;
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());

boundingBoxの呼び出しに対して、新しい、一時的なRectangleオブジェクトが取得されます.一時オブジェクトであり、upperleftが一時オブジェクトに作用し、referenceがtempの内部成分だけを返します.そこでpUpperLeftはそのPointオブジェクトを指す.しかし、その文が終わるとboundingBoxの戻り値、つまり私たちが言った一時オブジェクトが破棄され、最終的にpUpperLeftが存在しないオブジェクトを指します.
しかし、メンバー関数をhandleに戻すことは絶対にできないというわけではありません.時にはそうしなければなりません.たとえばoperator[]では、stringとvectorの個別要素を採取できます.これらのデータは、コンテナが破棄されるにつれて破棄されます.しかし、このような関数は例外であり、常態ではない.
覚えておいてください:
reference、ポインタ、反復器、オブジェクトの内部を指すhandlesを返さないでください.この条項を遵守すると、constメンバー関数の動作をconstのように支援し、dangling handleが発生する可能性が最小限に抑えられます.
条幅29:異常な安全のために努力する価値がある
(お母さん、私は2回書いて、下書きの中に置いて、すべて保存していないで、何がおかしいです!!!
条項30:inliningの裏表を徹底的に理解する
inline関数の背後にある全体的な概念は,この関数の各呼び出しを関数本体に置き換えることである.これにより、ターゲットコードのサイズが増加します.1台のメモリが限られているマシンでは、inlineに熱中しすぎるとプログラムの体積が大きくなり、仮想メモリを持っていても、inlineによるコードの膨張は追加のページ変更行為を招き、命令キャッシュ装置のヒット率を低下させる.
覚えておいてください.inlineはコンパイラに対する申請であり、強制命令ではありません.この申請は隠喩的に提出することも、明確に提出することもできます.隠喩的には、class定義内に関数を定義します.
class Person{
public:
	int age const{return theAge;}  //    inline  :age      class    。
private:
	int theAge;
};

生命inline関数を明確にする方法は、定義式の前にinlineキーワードを付けることであり、標準的なmax template(algorithmから)はこのように実現されることが多い.
template<typename T>
inline constT& std::max(const T& a, const T& b)
{return a < b ? b: a}

ほとんどのコンパイラは、ループや再帰などの複雑な関数inlineを拒否します.virtual関数の呼び出しはすべてinlineが空になります.virtualは、実行期間までどの関数を呼び出すかを決定するのを待つことを意味します.inlineは、実行前に呼び出し動作を呼び出された関数の本体に置き換えることを意味します.
表面的にinlineのように見える関数が本当にinlineであるかどうかは、あなたの構築環境に依存し、主にコンパイラに依存します.ほとんどのコンパイラは、あなたが要求する関数をinline化できない場合は、警告メッセージを与えます(条項53).
コンパイラはinlineのある関数を意図している場合がありますが、その関数のために関数本体を生成する可能性があります.例えば、プログラムがinline関数のアドレスを取得する場合、コンパイラは通常この関数のためにoutlined関数本体を生成しなければなりません.結局、コンパイラは存在しない関数を指すポインタを提出することができますか?
実際には、コンストラクション関数とコンストラクション関数はinlineの悪い候補です.
class Base{
public:
private:
	std::string bm1, bm2;
};
class Derived: public base{
public:
	Derived(){}   //Derived       ,  ?
private:
	std::string dm1, dm2, dm3;
};

C++は、オブジェクトが作成され、破棄されたときに何が起こるかを様々に保証しています.newを使用すると、動的に作成されたオブジェクトはその構造関数によって自動的に初期化されます.deleteを使用すると、対応する構造関数が呼び出されます.オブジェクトを作成すると、base classおよびメンバー変数ごとに自動的に構築されます.オブジェクトを破棄すると、逆プログラムの解析動作も自動的に発生します.オブジェクトの構造中に例外が投げ出されると、そのオブジェクトが構築された部分は自動的に破棄されます.したがって、コンパイラは、上記の表面上で空に見えるDerivedコンストラクタによって生成されるコードであり、次のようになります.
Derived::Derived(){
	Base::Base();
	try{dm1.std::string::string();}
	catch(...){
		Base::~Base();
		throw;
	}
	
	try{dm2.std::string::string();}
	catch(...){
		dm1.std::string::~string();
		Base::~Base();
		throw;
	}
	
	try{dm1.std::string::string();}
	catch(...){                                   //      
		dm2.std::string::~string();           //  dm2 dm1
		dm1.std::string::~string();
		Base::~Base();
		throw;
	}
	
}
これはDerivedの空白構造関数が提供しなければならない動作を正確に反映することができる.Derivedコンストラクション関数は、少なくともメンバー変数とbase classの両方のコンストラクション関数を連続的に呼び出す必要がありますが、それらの呼び出しはコンパイラがこの空白関数inlineに影響します.
同じ理由はbaseコンストラクション関数にも適用されるので、inlineされると、baseコンストラクション関数呼び出しを置き換えて挿入されたコードもderivedコンストラクション関数呼び出しに挿入されます.
ライブラリ設計者は、関数をinlineに生命させる衝撃を評価する必要があります.inline関数はプログラムのアップグレードに伴ってアップグレードできません.すなわち、fがライブラリ内のinline関数である場合、クライアントはf関数本体をプログラムにコンパイルし、プログラム設計者がfを変更することを決定すると、fを使用するすべてのクライアントプログラムを再コンパイルしなければならない.しかし、fがnoninline関数である場合、変更があれば、クライアントは再接続するだけでよい.
覚えておいてください:
1.ほとんどのinlineを小型で頻繁に呼び出される関数に制限します.これにより、今後のデバッグプロセスとバイナリアップグレードが容易になり、潜在的なコード膨張問題を最小化し、プログラムの速度向上機会を最大化することができます.
2.function templateがヘッダファイルに表示されるだけでなく、inlineとして宣言しないでください.
条項31:ファイル間のコンパイル依存関係を最小限に抑える(あまり読めない)
覚えておいてください:
1.コンパイル依存性の最小化をサポートする一般的な構想は、宣言式に依存し、定義式に依存しないことである.この構想に基づく2つの手段はhandle classesとInterface classesである.
2.ライブラリヘッダファイルは、templatesに関連するかどうかにかかわらず、完全かつ単純な形式で使用されるべきである.