『More Effective C+』読書ノート-技術
24550 ワード
25.コンストラクション関数と非メンバー関数を虚化する
1、ここでいう仮想コンストラクタとは、本当にコンストラクタの前にvirtual修飾子を付けることではなく、入力された異なるパラメータに基づいて異なる継承関係タイプを確立できるオブジェクトを指す.
以上のコードから分かるように、(1)NLComponentのConstructorは仮想化されていない.彼はreadComponentでistreamを読み取るだけで異なるタイプのコンポーネントを生成し、コンポーネントのポインタを返し、その後、ポインタをベースクラスポインタ(NLComponent*)タイプで格納し、後の呼び出しでマルチステートを実現することができる.これがVirtual Constructorである.(2)NewsLetterクラスのreadComponent関数は、入力された文字列によって異なるオブジェクトを生成します.新しいオブジェクトを生成するので、constructorのように動作しますが、異なるタイプのオブジェクトを生成することができるのでvirtual constructorと呼ばれます.virtual constructorとは、入力されたデータによって異なるタイプのオブジェクトを生成できることを意味します.!!!!
2、virtual copy constructor:virtual copy constructorとは、呼び出し元(オブジェクト)の新しいコピーを指すポインタを返します.
コードから分かるように、クラスのvirtual copy constructorは本当のcopy constructorを呼び出すだけです.また、実行する動作と効果は完全に一致します.
サブクラスがベースクラスの虚関数を再定義する場合、元の戻りタイプと同じ戻りタイプを宣言する必要はありません.関数の戻りタイプがベースクラスを指すポインタ(または参照)である場合、サブクラスの関数は、ベースクラスのサブクラスを指すポインタ(または参照)を返すことができます.
これは、NLComponentのclone関数の戻りタイプが
3、non-member functionsを虚化する
非メンバー関数の虚化は、virtualを使用して非メンバー関数を修飾するわけではありません.虚関数を1つ書いて実際の仕事をさせ、何もしない非虚関数を1つ書いて、虚関数を呼び出すだけです.
上記のコードから分かるように、印刷用として虚関数(print)を宣言し、TextBlockおよびGraphicで定義します.operator<<のnon-member functionを定義し、print虚関数のような一般的な動作を示します.
26、あるclassが生成できるオブジェクトの数を制限する
オブジェクトの個数を制限します:1つのベースクラスを創立して、構造関数と複製構造関数の中でカウントして1をプラスして、最大値を超えると異常を放出します;解析関数でカウントを1減らします.
27、要求(又は禁止)対象がheap中に発生する
1、要求対象がスタック内で発生する
スタック上のオブジェクトは、コンストラクションメソッドとコンストラクションメソッド(役割ドメインを離れたとき)を肯定的に呼び出すため、オブジェクトはheapでしか生成できないことが要求されます.つまり、スタック上のオブジェクトの生成を禁止します.解決策は、すべてのコンストラクションメソッドをprivateと宣言するか、コンストラクションメソッドをprivateと宣言するかの2つです.
構築メソッドまたはプロファイルメソッドをprivateとして宣言すると、継承と組み込み(組合せ)が阻止されるという2つの問題が発生します.
解決方法:継承の場合、親クラスのコンストラクション関数とコンストラクション関数にアクセス権を拡大できます.コンストラクション関数はprotectedで、コンストラクション関数はpublicを維持します.オブジェクトを含む場合は、オブジェクトを指すポインタを含むように変更します.
2、あるオブジェクトがheap内にあるかどうかを判断する
一つのオブジェクトがスタックの中にあるかどうかを判断する方法はありません.私たちはなぜオブジェクトがスタックの上にあるかどうかを判断しますか?実際のニーズはdeleteの実行が安全かどうかを判断することです.どうすればいいの?
オブジェクトがdeleteで安全に削除できるかどうかを判断するには、operator newでポインタをリストに追加し、このリストに基づいてポインタが存在するかどうかを判断します.存在する場合、deleteを実行するのは安全です.そうしないと安全ではありません.
3、どのようにオブジェクトがheapの中で発生することを禁止します
スタックにオブジェクトを作成するには、必ずoperator newを呼び出してメモリを割り当てるので、operator newをprivateと宣言すればいいです.アクセスレベルを統一するためにoperator deleteをprivateに設定できます.(ただし、スタック内にあるかどうかは判断できません).
28、Smart Pointer(スマートポインタ)
1、インテリジェントポインタと一般ポインタの違いは、インテリジェントポインタが実際には一般ポインタにパッケージメカニズムを追加していることであり、このようなパッケージメカニズムの目的は、インテリジェントポインタがオブジェクトのライフサイクルを容易に管理できるようにすることである.簡単に言えば、スマートポインタはポインタの動作をシミュレートするクラスです.インテリジェントポインタはテンプレートから生成されます.組み込みポインタと似ているため、強いタイプでなければなりません.テンプレートパラメータは、オブジェクトを指すタイプを決定します.
C++では、通常のポインタを使用してオブジェクトへのポインタを作成する場合、このオブジェクトを使用した後、自分で削除する必要がありますが、プログラマーが削除を忘れたり、削除前に異常を投げ出したりして、削除操作を実行しなかったりすると、メモリ漏洩が発生することを知っています.
このとき,インテリジェントポインタの出現は実際にはオブジェクトのライフサイクルを容易に制御するためであり,インテリジェントポインタでは,1つのオブジェクトがいつ,どのような条件で解析されるか,あるいは削除されるかはインテリジェントポインタ自体によって決定され,ユーザは管理する必要はない.
2、スマートポインタがNULLであるかどうかをテストするには、タイプ変換を使用してvoid*に変換する方法がありますが、異なるタイプのスマートポインタ間で比較できるため、タイプが安全ではありません.もう一つはoperatorをリロード!この案は使うしかない!ptrはこのように検出される.
3、スマートポインタの継承クラスからベースクラスへのタイプ変換:テンプレートメンバー関数を使用する.これにより、組み込みポインタのすべての変換可能なタイプをスマートポインタで変換できますが、間接的に継承する場合はdynamic_を使用する必要があります.castは、変換するタイプが直接ベースクラスか間接ベースクラスかを指定します.
29.Reference counting(参照数)
1、参照カウントを使用すると、オブジェクトは自分自身を持ち、誰も使用しない場合は自分で自動的に破棄されます.したがって,参照カウントは簡単なごみ回収システムである.このカウントには2つの動機があります.1つ目は、スタックオブジェクトの周辺の簿記作業を簡略化することです.第二に、すべての等値オブジェクトが同じ実値を共有し、メモリを節約するだけでなく、プログラムの速度を速めるという常識を実現するためです.
参照カウントの最も重要な機能は、オブジェクト共有です.多くのオブジェクトが同じ値を持っている場合、その値を複数回保存するのは愚かです.したがって、すべての等値オブジェクトに実値を共有させることで要求を満たすことができ、メモリ領域を節約することができます.スピードを上げて(オブジェクトの構築、分析にかかる時間).参照カウントされたオブジェクトの共有数を追跡する必要があります.共有オブジェクトが変更された場合、参照カウントオブジェクトを変更することはできません.他の共有オブジェクトが必要であるためです.このとき、参照カウントは役に立ちます.これも参照カウントに追加しなければならないオーバーヘッドです.つまり、私共有されたオブジェクトを格納する必要があり、そのオブジェクトの参照回数を保存する必要があります.両者は結合関係です.
2、引用カウントのコスト:引用カウント能力を持つ実値ごとに1つの引用カウンタを携帯し、大部分の操作はこのカウンタを検証または処理する必要があり、オブジェクトの実値はより多くのメモリを必要とし、より多くのコードを実行する必要がある.
3、参照カウントの利点:参照カウントは最適化技術であり、オブジェクトが常に実値を共有することを前提としており、この場合、スペースと時間を節約できます.以下に、参照カウントの効率化に最適なタイミングを示します.(1)比較的多数のオブジェクトが比較的少量の実値を共有している場合."オブジェクト/実値"の数が高いほど、参照カウントによる利益が大きくなります.(2)オブジェクトの実値の生成や破棄にコストがかかる場合、またはメモリを多く使用している場合.
参照カウント実装のStringクラス:(参照)http://blog.csdn.net/ruan875417/article/details/48241525)
1、ここでいう仮想コンストラクタとは、本当にコンストラクタの前にvirtual修飾子を付けることではなく、入力された異なるパラメータに基づいて異なる継承関係タイプを確立できるオブジェクトを指す.
class NLComponent { // ,
public:
...
};
class TextBlock: public NLComponent{ //
public:
...
};
class Graphic: public NLComponent{ //
public:
...
};
class NewsLetter { // NLComponent
public:
NewsLetter(istream& str);//NewsLetter istream
// , stream
...
private:
// str NLComponent , , !
static NLComponent* readComponent(istream &str);
list<NLComponent *> components;
};
NewsLetter::NewsLetter(istream &str)
{
while(str)
{// readComponent Component list
components.push_back(readComponent(str));
}
}
以上のコードから分かるように、(1)NLComponentのConstructorは仮想化されていない.彼はreadComponentでistreamを読み取るだけで異なるタイプのコンポーネントを生成し、コンポーネントのポインタを返し、その後、ポインタをベースクラスポインタ(NLComponent*)タイプで格納し、後の呼び出しでマルチステートを実現することができる.これがVirtual Constructorである.(2)NewsLetterクラスのreadComponent関数は、入力された文字列によって異なるオブジェクトを生成します.新しいオブジェクトを生成するので、constructorのように動作しますが、異なるタイプのオブジェクトを生成することができるのでvirtual constructorと呼ばれます.virtual constructorとは、入力されたデータによって異なるタイプのオブジェクトを生成できることを意味します.!!!!
2、virtual copy constructor:virtual copy constructorとは、呼び出し元(オブジェクト)の新しいコピーを指すポインタを返します.
class NLComponent{
public:
// virtual copy constructor
virtual NLComponent *clone() const = 0;
};
class TextBlock:public NLComponent{
public:
virtual TextBlock* clone() const
{ return new TextBlock(*this);}
};
class Graphic:public NLComponent {
public:
virtual Graphic* clone() const
{return new Graphic(*this);}
};
コードから分かるように、クラスのvirtual copy constructorは本当のcopy constructorを呼び出すだけです.また、実行する動作と効果は完全に一致します.
サブクラスがベースクラスの虚関数を再定義する場合、元の戻りタイプと同じ戻りタイプを宣言する必要はありません.関数の戻りタイプがベースクラスを指すポインタ(または参照)である場合、サブクラスの関数は、ベースクラスのサブクラスを指すポインタ(または参照)を返すことができます.
これは、NLComponentのclone関数の戻りタイプが
NLComponent*
であっても、TextBlockのclone関数はTextBlock*
を返し、Graphicのclone関数はGraphic*を返すことができる理由である.3、non-member functionsを虚化する
非メンバー関数の虚化は、virtualを使用して非メンバー関数を修飾するわけではありません.虚関数を1つ書いて実際の仕事をさせ、何もしない非虚関数を1つ書いて、虚関数を呼び出すだけです.
#include<iostream>
using namespace std;
class NLComponent{
public:
virtual ostream& print(ostream& s) const = 0;
};
class TextBlock:public NLComponent{
public:
virtual ostream& print(ostream& s) const
{
s << "TextBlock";
return s;
}
};
class Graphic : public NLComponent{
public:
virtual ostream& print(ostream& s) const
{
s << "Graphic";
return s;
}
};
inline ostream& operator<<(ostream& s, const NLComponent& c)
{
return c.print(s);
}
int main(){
TextBlock tx;
Graphic gc;
cout << tx << endl;
cout << gc << endl;
return 0;
}
上記のコードから分かるように、印刷用として虚関数(print)を宣言し、TextBlockおよびGraphicで定義します.operator<<のnon-member functionを定義し、print虚関数のような一般的な動作を示します.
26、あるclassが生成できるオブジェクトの数を制限する
オブジェクトの個数を制限します:1つのベースクラスを創立して、構造関数と複製構造関数の中でカウントして1をプラスして、最大値を超えると異常を放出します;解析関数でカウントを1減らします.
27、要求(又は禁止)対象がheap中に発生する
1、要求対象がスタック内で発生する
スタック上のオブジェクトは、コンストラクションメソッドとコンストラクションメソッド(役割ドメインを離れたとき)を肯定的に呼び出すため、オブジェクトはheapでしか生成できないことが要求されます.つまり、スタック上のオブジェクトの生成を禁止します.解決策は、すべてのコンストラクションメソッドをprivateと宣言するか、コンストラクションメソッドをprivateと宣言するかの2つです.
構築メソッドまたはプロファイルメソッドをprivateとして宣言すると、継承と組み込み(組合せ)が阻止されるという2つの問題が発生します.
class UPNumber { ... }; // private
//
class NonNegativeUPNumber: public UPNumber { ... }; // !
class Asset {
private:
UPNumber value;//
... // !
};
解決方法:継承の場合、親クラスのコンストラクション関数とコンストラクション関数にアクセス権を拡大できます.コンストラクション関数はprotectedで、コンストラクション関数はpublicを維持します.オブジェクトを含む場合は、オブジェクトを指すポインタを含むように変更します.
2、あるオブジェクトがheap内にあるかどうかを判断する
一つのオブジェクトがスタックの中にあるかどうかを判断する方法はありません.私たちはなぜオブジェクトがスタックの上にあるかどうかを判断しますか?実際のニーズはdeleteの実行が安全かどうかを判断することです.どうすればいいの?
オブジェクトがdeleteで安全に削除できるかどうかを判断するには、operator newでポインタをリストに追加し、このリストに基づいてポインタが存在するかどうかを判断します.存在する場合、deleteを実行するのは安全です.そうしないと安全ではありません.
3、どのようにオブジェクトがheapの中で発生することを禁止します
スタックにオブジェクトを作成するには、必ずoperator newを呼び出してメモリを割り当てるので、operator newをprivateと宣言すればいいです.アクセスレベルを統一するためにoperator deleteをprivateに設定できます.(ただし、スタック内にあるかどうかは判断できません).
28、Smart Pointer(スマートポインタ)
1、インテリジェントポインタと一般ポインタの違いは、インテリジェントポインタが実際には一般ポインタにパッケージメカニズムを追加していることであり、このようなパッケージメカニズムの目的は、インテリジェントポインタがオブジェクトのライフサイクルを容易に管理できるようにすることである.簡単に言えば、スマートポインタはポインタの動作をシミュレートするクラスです.インテリジェントポインタはテンプレートから生成されます.組み込みポインタと似ているため、強いタイプでなければなりません.テンプレートパラメータは、オブジェクトを指すタイプを決定します.
C++では、通常のポインタを使用してオブジェクトへのポインタを作成する場合、このオブジェクトを使用した後、自分で削除する必要がありますが、プログラマーが削除を忘れたり、削除前に異常を投げ出したりして、削除操作を実行しなかったりすると、メモリ漏洩が発生することを知っています.
このとき,インテリジェントポインタの出現は実際にはオブジェクトのライフサイクルを容易に制御するためであり,インテリジェントポインタでは,1つのオブジェクトがいつ,どのような条件で解析されるか,あるいは削除されるかはインテリジェントポインタ自体によって決定され,ユーザは管理する必要はない.
2、スマートポインタがNULLであるかどうかをテストするには、タイプ変換を使用してvoid*に変換する方法がありますが、異なるタイプのスマートポインタ間で比較できるため、タイプが安全ではありません.もう一つはoperatorをリロード!この案は使うしかない!ptrはこのように検出される.
3、スマートポインタの継承クラスからベースクラスへのタイプ変換:テンプレートメンバー関数を使用する.これにより、組み込みポインタのすべての変換可能なタイプをスマートポインタで変換できますが、間接的に継承する場合はdynamic_を使用する必要があります.castは、変換するタイプが直接ベースクラスか間接ベースクラスかを指定します.
29.Reference counting(参照数)
1、参照カウントを使用すると、オブジェクトは自分自身を持ち、誰も使用しない場合は自分で自動的に破棄されます.したがって,参照カウントは簡単なごみ回収システムである.このカウントには2つの動機があります.1つ目は、スタックオブジェクトの周辺の簿記作業を簡略化することです.第二に、すべての等値オブジェクトが同じ実値を共有し、メモリを節約するだけでなく、プログラムの速度を速めるという常識を実現するためです.
参照カウントの最も重要な機能は、オブジェクト共有です.多くのオブジェクトが同じ値を持っている場合、その値を複数回保存するのは愚かです.したがって、すべての等値オブジェクトに実値を共有させることで要求を満たすことができ、メモリ領域を節約することができます.スピードを上げて(オブジェクトの構築、分析にかかる時間).参照カウントされたオブジェクトの共有数を追跡する必要があります.共有オブジェクトが変更された場合、参照カウントオブジェクトを変更することはできません.他の共有オブジェクトが必要であるためです.このとき、参照カウントは役に立ちます.これも参照カウントに追加しなければならないオーバーヘッドです.つまり、私共有されたオブジェクトを格納する必要があり、そのオブジェクトの参照回数を保存する必要があります.両者は結合関係です.
2、引用カウントのコスト:引用カウント能力を持つ実値ごとに1つの引用カウンタを携帯し、大部分の操作はこのカウンタを検証または処理する必要があり、オブジェクトの実値はより多くのメモリを必要とし、より多くのコードを実行する必要がある.
3、参照カウントの利点:参照カウントは最適化技術であり、オブジェクトが常に実値を共有することを前提としており、この場合、スペースと時間を節約できます.以下に、参照カウントの効率化に最適なタイミングを示します.(1)比較的多数のオブジェクトが比較的少量の実値を共有している場合."オブジェクト/実値"の数が高いほど、参照カウントによる利益が大きくなります.(2)オブジェクトの実値の生成や破棄にコストがかかる場合、またはメモリを多く使用している場合.
参照カウント実装のStringクラス:(参照)http://blog.csdn.net/ruan875417/article/details/48241525)
#include<iostream>
#include<string.h>
using namespace std;
class String{
public:
String(const char* initValue = nullptr);//
String(const String& rhs);//
~String();//
String& operator=(const String& rhs);//
const char& operator[](int index) const;// [] , const Strings
char& operator[](int index);// [] , non-const Strings
String operator+(const String& rhs);// +
String& operator+=(const String& rhs);// +=
bool operator==(const String& rhs);// ==
int getLength();//
friend istream& operator>>(istream& is, const String& str);// >>
friend ostream& operator<<(ostream& os, const String& str);// <<
int getRefCount();//
private:
struct StringValue{
int refCount;//
char* data;
StringValue(const char* initValue);//
~StringValue();//
};
StringValue* value;
};
//StringValue
String::StringValue::StringValue(const char* initValue):refCount(1){
if (initValue == nullptr){
data = new char[1];
data[0] = '\0';
}
else{
data = new char[strlen(initValue) + 1];
strcpy(data, initValue);
}
}
//StringValue
String::StringValue::~StringValue(){
delete[] data;
data = nullptr;
}
//String
String::String(const char* initValue):value(new StringValue(initValue))
{}
//String
String::String(const String& rhs) : value(rhs.value){
++value->refCount;// 1!!!
}
//String
String::~String(){
if (--value->refCount == 0){// 1, 0 , ,
delete value;
}
}
//String
String& String::operator=(const String& rhs){
if (value == rhs.value) //
return *this;
// 1, 0 , ,
if (--value->refCount == 0)
delete value;
// , , 1
value = rhs.value;
++value->refCount;
return *this;
}
// [] , const Strings
const char& String::operator[](int index) const{
if(index<strlen(value->data))
return value->data[index];
}
// [] , non-const Strings
char& String::operator[](int index){
if (value->refCount>1)
{// String ,
// ( )
--value->refCount;
value = new StringValue(value->data);
}
if (index<strlen(value->data))
return value->data[index];
}
//String +
String String::operator+(const String& rhs){
return String(*this) += rhs;
}
//String +=
String& String::operator+=(const String& rhs){
// 1, 0 , ,
if (--value->refCount == 0)
delete value;
//
if (rhs.value->data == nullptr){
value = new StringValue(value->data);
return *this;
}
//
if (this->value->data == nullptr){
value = new StringValue(rhs.value->data);
return *this;
}
//
char* pTemp = new char[strlen(this->value->data) + strlen(rhs.value->data) + 1];
strcpy(pTemp, this->value->data);
strcat(pTemp, rhs.value->data);
value=new StringValue(pTemp);
return *this;
}
// ==
bool String::operator==(const String& rhs){
return strcmp(this->value->data, rhs.value->data) == 0 ? true : false;
}
//
int String::getLength(){
return strlen(this->value->data);
}
// >>
istream& operator>>(istream& is, const String& str){
is >> str.value->data;
return is;
}
// <<
ostream& operator<<(ostream& os, const String& str){
os << str.value->data;
return os;
}
//
int String::getRefCount(){
return value->refCount;
}
int main()
{
String str1("hello world");
String str2 = str1;//
String str3;//
str3 = str2;//
cout << "str1 :" << str1.getRefCount() << endl; // 3
cout << "str2 :" << str2.getRefCount() << endl; // 3
cout << "str3 :" << str3.getRefCount() << endl; // 3
str1[0] = 'H';// non-const Strings []
cout << str1 << endl; //"Hello world"
cout << str2 << endl;//"hello world"
cout << str3 << endl;//"hello world"
cout << "str1 :" << str1.getRefCount() << endl;//1
cout << "str2 :" << str2.getRefCount() << endl;//2
cout << "str3 :" << str3.getRefCount() << endl;//2
String str4("hello");//
String str5 = str4;//
String str6 = " world";//
str5 = str5+str6;// String + , String
cout << str4 << endl; //"hello"
cout << str5 << endl; //"hello world"
cout << str6 << endl; //" world"
cout << "str4 :" << str4.getRefCount() << endl;//1
cout << "str5 :" << str5.getRefCount() << endl;//1
cout << "str6 :" << str6.getRefCount() << endl;//1
String str7 = str5;//
String str8;//
str8 = str7;// String
cout << str7 << endl; //"hello world"
cout << "str5 :" << str5.getRefCount() << endl;//3
cout << "str7 :" << str7.getRefCount() << endl;//3
cout << "str8 :" << str8.getRefCount() << endl;//3
str5 += str6;// String +=
cout << str5 << endl; //"hello world world"
cout << str6 << endl; //" world"
cout << str7 << endl; //"hello world"
cout << str8 << endl; //"hello world"
cout << "str5 :" << str5.getRefCount() << endl; //1
cout << "str6 :" << str6.getRefCount() << endl;//1
cout << "str7 :" << str7.getRefCount() << endl;//2
cout << "str8 :" << str8.getRefCount() << endl;//2
return 0;
}