Big Three & operator new/delete[GeekBand]

1.Big Three

當我們定義一個類以后有,如果沒實現(xiàn)這拷貝構造函數(shù)、拷貝復制函數(shù)和析構函數(shù),編譯器會自動為我們生成這3個函數(shù)。但是,編譯器自動生成的這拷貝構造函數(shù)和拷貝復制函數(shù)只進行簡單的內(nèi)存復制。如果我們定義的類的數(shù)據(jù)成員包括指針的話,使用編譯器自動生成的這套函數(shù)就會有問題(復制和拷貝的對象實際都指向同一個地方,這并不是我們想要的),因此如果類中包含帶指針的數(shù)據(jù)成員則一定要實現(xiàn)這三個函數(shù)。

1.1拷貝構造函數(shù)(copy ctor)

如果一個構造函數(shù)的第一個參數(shù)是自身類類型的引用,且任何額外的參數(shù)都有默認值,則此構造函數(shù)是拷貝構造函數(shù)??截悩嬙旌瘮?shù)被調(diào)用的情況包括:

  • a)使用=定義變量,如果A b; A a = b;
  • b)將一個對象作為實參傳遞給非引用類型的形參;
  • c)從一個返回類型為非引用的類型的函數(shù)返回一個對象
  • d)用花括號初始化一個數(shù)組的成員或者聚合類的成員

1.2拷貝賦值函數(shù)(copy op=)

拷貝賦值函數(shù),其實就是對賦值操作符(=)進行重載。一般,拷貝賦值函數(shù)接受自身類類型的引用作為參數(shù)并返回自身類類型的引用。如:A& operator=(const A& rhs);

1.3析構函數(shù)(dtor)

析構函數(shù),釋放對象使用的資源,并銷毀對象的非static數(shù)據(jù)成員。析構函數(shù)被調(diào)用的情況:

  • a)離開作用域
  • b)對象被銷毀
  • c)容器被銷毀時,其元素也被銷毀
  • d)動態(tài)分配的對象,調(diào)用對應的delete
  • e)對于臨時對象, 當創(chuàng)建他的表達式結束時。

2.stack & heap

2.1 stack & heap 定義

  • a)stack
    所謂的stack,是存在于某作用域(scope)的一塊內(nèi)存空間。
  • b)heap
    Heap,或謂system heap,是由操作系統(tǒng)提供的一塊global內(nèi)存空間,程序可動態(tài)分配從中獲取若干區(qū)塊。

2.2 new & delete

2.2.1 new

c++可以使用new來分配內(nèi)存空間。new的用法有三種,包括:

  • a)plain new
    plain new其實就是我們最常使用的方式,new分配內(nèi)存失敗時并不是返回NULL,而是拋出異常std::bad_alloc.std::bad_alloc和new的聲明在頭文件new中。
    函數(shù)聲明:
void* operator new(std::size_t) _GLIBCXX_THROW (std::bad_alloc)  __attribute__((__externally_visible__));
void* operator new[](std::size_t) _GLIBCXX_THROW (std::bad_alloc)  __attribute__((__externally_visible__));

用法:

#include <new>
std::size_t sz = 100;
char *parr = NULL;
char *p = NULL;
try{
    parr = new char[sz];
    p = new char;
}catch(std::bad_alloc& ex){
    std::cout << ex.what() << std::endl;
}
  • b)nothrow new
    nothrow new和plain new唯一的不同在于,new分配失敗后并不拋出異常,而是返回NULL.同樣nothrow new也在頭文件<new>中聲明。函數(shù)聲明:
void* operator new(std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT  __attribute__((__externally_visible__));
void* operator new[](std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT  __attribute__((__externally_visible__));

用法:

#include <new>
#include <iostream>
std::size_t sz = 100;
char *parr = new(nothrow) char[sz];
if (!parr){
    std::cout << "new error" << std::endl;
}
char *p = new(nothrow) char;
if (!p){
    std::cout << "new error" << std::endl;
}

說明:nothrow是頭文件聲明的對象,可以直接使用。

  • c)placement new

函數(shù)聲明:

inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT;
inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT;

用法:

void *buffer = new char[sz];
String *p = new(buffer) String[2];

按照標準庫<new>中的分類,plain new和nothrow new是replaceable,用戶可以通過重載操作符來實現(xiàn)自定義的內(nèi)存管理,placement new是不可重載的操作。

  • plain new和nothrow new執(zhí)行的動作如下:
    1.分配空間
    2.初始化對象
    比如說,我們執(zhí)行Complex *p = new Complex(1, 2);等價于如下代碼:
void *mem = operator new(sizeof(Complex));  //分配空間
pc = static_cast<Complex *>men; //類型轉換
pc->Complex::Complex(1,2); //初始化對象
  • placement new執(zhí)行的動作如下:
    1初始化對象
    operator new并沒有進行內(nèi)存空間的分配,只是返回之前分配的空間指針。在<new>頭文件中,有一份placement new的默認實現(xiàn)。代碼如下:
// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }

2.2.2 delete

c++刪除動態(tài)分配的Heap內(nèi)存使用delete。delete的用法也和new一樣存在三種且一一對應。
聲明:

//plain delete
void operator delete(void*) ;
void operator delete[](void*) ;
//nothrow delete
void operator delete(void*, const std::nothrow_t&) ;
void operator delete[](void*, const std::nothrow_t&) ;
//placement delete
inline void operator delete  (void*, void*);
inline void operator delete[](void*, void*) ;

雖然delete依然有三類聲明,但是只有plain delete可以通過delete-expression的方式使用。nothrow delete和placement delete只在相應的new-expression拋出異常時由系統(tǒng)自動調(diào)用。如果想手動使用nothrow delete和placement delete可以通過函數(shù)調(diào)用的方式。
nothrow delete和placement delete的函數(shù)使用方式:

operator delete(p, nothrow);
operator delete(buff, p);

2.3 array new與array delete探析

array new和array delete要配對使用,如果不配對使用可能出現(xiàn)內(nèi)存泄漏。出現(xiàn)內(nèi)存泄漏的原因并不是因為delete不能完全釋放array new分配的內(nèi)存,而是因為使用delete釋放array new分配的內(nèi)存時,編譯器只對array new分配的數(shù)組的某一個對調(diào)用析構函數(shù)。這樣就會導致數(shù)組中的其他對象因為沒有調(diào)用析構函數(shù)而導致內(nèi)存沒有正確釋放。
下面通過一個例子來說明這個問題。
代碼:

void ArrayNewAndArrayDeleteTest()
{
    cout << "ArrayNewAndDelete:" << endl;
    String *p1 = new String[3];
    delete p1;
    cout << "ArrayNewAndArrayDelete:" << endl;
    String *p2 = new String[3];
    delete[] p2;
}

運行結果:

****begin run function ArrayNewAndArrayDeleteTest *****
ArrayNewAndDelete:
normal ctor:null string
normal ctor:null string
normal ctor:null string
dtor:
ArrayNewAndArrayDelete:
normal ctor:null string
normal ctor:null string
normal ctor:null string
dtor:
dtor:
dtor:
****end run function ArrayNewAndArrayDeleteTest ****

從結果可以看出,

  • 使用array new分配數(shù)組p1時調(diào)用了3次構造函數(shù)(3個normal ctor輸出),使用delete釋放內(nèi)存時只調(diào)用了一次析構函數(shù)(只有一次dtor輸出);
  • 使用array new分配數(shù)組p2時調(diào)用了3次構造函數(shù)(3個normal ctor輸出),使用array delete釋放內(nèi)存時調(diào)用了3次析構函數(shù)(3個dtor輸出)
    綜上,array new配合delete,會導致析構函數(shù)只調(diào)用一次,從而導致內(nèi)存泄漏。

3.其他

編譯器按照數(shù)據(jù)成員的聲明順序初始化對象,而不是按照初始化列表聲明的順序初始化對象。構造函數(shù)的初始化列表最好和類的數(shù)據(jù)成員的聲明順序一致,且最好不要使用類的其他成員來初始化類的成員。

說明:
1.本文使用的String對象為侯老師上課所用String class,只是對函數(shù)的調(diào)用添加輸出語句。
2.使用編譯器為g++ 4.8.1
引用:
1.http://www.cnblogs.com/resound/archive/2011/10/27/2226472.html
2.GeekBand課件
3.cpp primer 5th edition
4.http://en.cppreference.com/w/cpp/memory/new/operator_delete
5.http://en.cppreference.com/w/cpp/memory/new/operator_new

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容