effectiveC++ 內存管理 學習筆記


1.儘可能使用初始化列表而不要再構造函數裏賦值,初始化順序和聲明的順序一致,一些類型如const,引用等,必須使用初始化。對於非內部數據類型成員對象應當採用初始化表,以獲取更高的效率。
example:
B::B(const A& a):m_a(a){}只調用了類A的拷貝構造函數
2.基類都使用虛析構函數,這樣才能在使用多態時,準確的析構派生類
3.operator>>和operator<<決不能是成員函數。若是f是operator>>或operator<<,讓f成爲非成員函數。若是f還須要訪問c的非公有成員,讓f成爲c的友元函數。在類的內部,它能夠用於靜態和非靜態成員。
4.儘量使用const.const關鍵字實在是神通廣大。在類的外面,它能夠用於全局或名字空間常量,以及靜態對象(某一文件或程序塊範圍內的局部對象)。在類的內部,它能夠用於靜態和非靜態成員.
對指針來講,能夠指定指針自己爲const,也能夠指定指針所指的數據爲const,或兩者同時指定爲const,還有,二者都不指定爲const:
char *p = "hello"; // 非const指針,
// 非const數據
const char *p = "hello"; // 非const指針,
// const數據
char * const p = "hello"; // const指針,
// 非const數據
const char * const p = "hello"; // const指針,
// const數據
5.儘可能用「傳引用」而不用「傳值」
「經過值來傳遞一個對象」的具體含義是由這個對象的類的拷貝構造函數定義的。這使得傳值成爲一種很是昂貴的操做。
爲避免這種潛在的昂貴的開銷,就不要經過值來傳遞對象,而要經過引用:
const student& returnstudent(const student& s)
{ return s; }
這會很是高效:沒有構造函數或析構函數被調用,由於沒有新的對象被建立。函數

6.返回值:對於賦值函數,應當用「引用傳遞」的方式返回對象,對於相加函數應當用值傳遞,由於引用對象在函數結束時被銷燬。ui

例如:this

class String  
{…  
    // 賦值函數  
    String & operate=(const String &other);   
        // 相加函數,若是沒有friend修飾則只許有一個右側參數  
        friend  String   operate+( const String &s1, const String &s2);   
private:  
    char *m_data;   
}  
  
//String的賦值函數operate = 的實現以下:  
String & String::operate=(const String &other)  
{  
    if (this == &other)  
        return *this;  
    delete m_data;  
    m_data = new char[strlen(other.data)+1];  
    strcpy(m_data, other.data);  
    return *this;   // 返回的是 *this的引用,無需拷貝過程  
}  

對於賦值函數,應當用「引用傳遞」的方式返回String對象。若是用「值傳遞」的方式,雖然功能仍然正確,但因爲return語句要把 *this拷貝到保存返回值的外部存儲單元之中,增長了沒必要要的開銷,下降了賦值函數的效率。例如:spa

String a,b,c;

a = b; // 若是用「值傳遞」,將產生一次 *this 拷貝
a = b= c; // 若是用「值傳遞」,將產生兩次 *this 拷貝
對於相加函數,應當用「值傳遞」的方式返回String對象。若是改用「引用傳遞」,那麼函數返回值是一個指向局部對象temp的「引用」。因爲temp是在棧上申請的變量,函數執行完畢後被銷燬,將致使返回的「引用」無效。指針

//String的相加函數operate + 的實現以下:  
String  operate+(const String &s1, const String &s2)    
{  
    String temp;  
    delete temp.data;   // temp.data是僅含‘\0’的字符串  
    temp.data = new char[strlen(s1.data) + strlen(s2.data) +1];  
    strcpy(temp.data, s1.data);  
    strcat(temp.data, s2.data);  
    return temp;  
}  

若是函數返回值是一個對象,要考慮return語句的效率。例如
return String(s1 + s2);
這是臨時對象的語法,表示「建立一個臨時對象並返回它」。不要覺得它與「先建立一個局部對象temp並返回它的結果」是等價的,如
String temp(s1 + s2);
return temp;
實質否則,上述代碼將發生三件事。首先,temp對象被建立,同時完成初始化;而後拷貝構造函數把temp拷貝到保存返回值的外部存儲單元中;最後,temp在函數結束時被銷燬(調用析構函數)。然而「建立一個臨時對象並返回它」的過程是不一樣的,編譯器直接把臨時對象建立並初始化在外部存儲單元中,省去了拷貝和析構的化費,提升了效率。code

7.劃分全局名字空間的好處
例如,假設library1.h定義了一些常量,其中包括:對象

const double lib_version = 1.204;blog

相似的,library2.h也定義了:接口

const int lib_version = 3;內存

很顯然,若是某個程序想同時包含library1.h和library2.h就會有問題。

要這麼作:

namespace sdm {
  const double book_version = 2.0;
  class handle { ... };
  handle& gethandle();
}

用戶因而能夠經過三種方法來訪問這一名字空間裏的符號:將名字空間中的全部符號所有引入到某一用戶空間;將部分符號引入到某一用戶空間;或經過修飾符顯式地一次性使用某個符號:

void f1()
{
  using namespace sdm;           // 使得sdm中的全部符號不用加
                                 // 修飾符就可使用

  cout << book_version;          // 解釋爲sdm::book_version
  ...

  handle h = gethandle();        // handle解釋爲sdm::handle,
                                 // gethandle解釋爲sdm::gethandle
  ...                            

}

void f2()
{
  using sdm::book_version;        // 使得僅book_version不用加
                                 // 修飾符就可使用

  cout << book_version;           // 解釋爲
                                  // sdm::book_version
  ...

  handle h = gethandle();         // 錯誤! handle和gethandle
                                  // 都沒有引入到本空間
  ...                             

}

void f3()
{
  cout << sdm::book_version;      // 使得book_version
                                  // 在本語句有效
  ...                             

  double d = book_version;        // 錯誤! book_version
                                  // 不在本空間

  handle h = gethandle();         // 錯誤! handle和gethandle
                                  // 都沒有引入到本空間
  ...                            

}

8.將文件間的編譯依賴性降至最低
假設某一天你打開本身的C++程序代碼,而後對某個類的實現作了小小的改動。提醒你,改動的不是接口,而是類的實現,也就是說,只是細節部分。而後你準備從新生成程序,心想,編譯和連接應該只會花幾秒種。畢竟,只是改動了一個類嘛!因而你點擊了一下"Rebuild",或輸入make(或其它相似命令)。然而,等待你的是驚愕,接着是痛苦。由於你發現,整個世界都在被從新編譯、從新連接!
在name.h中定義
class name{};
在person.h中:

#include"name.h"
class person{
public:
person(const name& _pname);
virtual ~Person();
private:
name _pname;//實現細節
string namestr() const;
};

在其餘文件中:
class man:public person{
...
};
class woman:public person{
...
};

這時候person文件和name.h之間創建了編譯依賴關係,若是name改變了它的實現,或者name依賴的類改變了實現,包含person類的文件以及任何使用了person類的文件就必須從新編譯。這時person,man,和woman的文件都要從新編譯。
爲了實現定義與實現細節分開,咱們能夠這樣定義person:

class name;//提早聲明
class person{
public:
person(const name& _pname);
virtual ~Person();
private:
string namestr() const;
};

若是這樣作可行,person的用戶就不須要從新編譯,只惋惜用起來才知道:

int main()
{
int x;//定義一個int
person p(new name);//定義一個person
}

當看到x的定義時,編譯器知道必須爲它分配一個int大小的內存。這沒問題,每一個編譯器都知道一個int有多大。然而,當看到p的定義時,編譯器雖然知道必須爲它分配一個person大小的內存,但怎麼知道一個Person對象有多大呢?
對應於上面的代碼,或許能夠這樣作:

int main()
{
  int x;                     // 定義一個int
  Person *p;                 // 定義一個Person指針,編譯器知道是4字節
  ...
}

下面具體介紹怎麼採用這一技術來實現person接口和實現的分離:

class name;//提早聲明
class man;
class woman;
class person{
public:
person(const name& _pname);
virtual ~Person();
man *personimpl1;//指向具體實現類
woman *personimpl2;
string namestr() const;
};
//在person.cpp中:
#include"person.h"
#include"man.h"
person.cpp::person(const name& _pname)
{
personimpl1 = new man(_pname);
}
string person::namestr()const
{
return personimpl1->name();
}

這樣,person做爲接口與實現徹底分離。編譯時不對name的改變產生依賴

相關文章
相關標籤/搜索