1.Big Three
當(dāng)我們定義一個(gè)類(lèi)以后有,如果沒(méi)實(shí)現(xiàn)這拷貝構(gòu)造函數(shù)、拷貝復(fù)制函數(shù)和析構(gòu)函數(shù),編譯器會(huì)自動(dòng)為我們生成這3個(gè)函數(shù)。但是,編譯器自動(dòng)生成的這拷貝構(gòu)造函數(shù)和拷貝復(fù)制函數(shù)只進(jìn)行簡(jiǎn)單的內(nèi)存復(fù)制。如果我們定義的類(lèi)的數(shù)據(jù)成員包括指針的話,使用編譯器自動(dòng)生成的這套函數(shù)就會(huì)有問(wèn)題(復(fù)制和拷貝的對(duì)象實(shí)際都指向同一個(gè)地方,這并不是我們想要的),因此如果類(lèi)中包含帶指針的數(shù)據(jù)成員則一定要實(shí)現(xiàn)這三個(gè)函數(shù)。
1.1拷貝構(gòu)造函數(shù)(copy ctor)
如果一個(gè)構(gòu)造函數(shù)的第一個(gè)參數(shù)是自身類(lèi)類(lèi)型的引用,且任何額外的參數(shù)都有默認(rèn)值,則此構(gòu)造函數(shù)是拷貝構(gòu)造函數(shù)。拷貝構(gòu)造函數(shù)被調(diào)用的情況包括:
- a)使用=定義變量,如果A b; A a = b;
- b)將一個(gè)對(duì)象作為實(shí)參傳遞給非引用類(lèi)型的形參;
- c)從一個(gè)返回類(lèi)型為非引用的類(lèi)型的函數(shù)返回一個(gè)對(duì)象
- d)用花括號(hào)初始化一個(gè)數(shù)組的成員或者聚合類(lèi)的成員
1.2拷貝賦值函數(shù)(copy op=)
拷貝賦值函數(shù),其實(shí)就是對(duì)賦值操作符(=)進(jìn)行重載。一般,拷貝賦值函數(shù)接受自身類(lèi)類(lèi)型的引用作為參數(shù)并返回自身類(lèi)類(lèi)型的引用。如:A& operator=(const A& rhs);
1.3析構(gòu)函數(shù)(dtor)
析構(gòu)函數(shù),釋放對(duì)象使用的資源,并銷(xiāo)毀對(duì)象的非static數(shù)據(jù)成員。析構(gòu)函數(shù)被調(diào)用的情況:
- a)離開(kāi)作用域
- b)對(duì)象被銷(xiāo)毀
- c)容器被銷(xiāo)毀時(shí),其元素也被銷(xiāo)毀
- d)動(dòng)態(tài)分配的對(duì)象,調(diào)用對(duì)應(yīng)的delete
- e)對(duì)于臨時(shí)對(duì)象, 當(dāng)創(chuàng)建他的表達(dá)式結(jié)束時(shí)。
2.stack & heap
2.1 stack & heap 定義
- a)stack
所謂的stack,是存在于某作用域(scope)的一塊內(nèi)存空間。 - b)heap
Heap,或謂system heap,是由操作系統(tǒng)提供的一塊global內(nèi)存空間,程序可動(dòng)態(tài)分配從中獲取若干區(qū)塊。
2.2 new & delete
2.2.1 new
c++可以使用new來(lái)分配內(nèi)存空間。new的用法有三種,包括:
- a)plain new
plain new其實(shí)就是我們最常使用的方式,new分配內(nèi)存失敗時(shí)并不是返回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;
}
說(shuō)明:nothrow是頭文件聲明的對(duì)象,可以直接使用。
- 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];
按照標(biāo)準(zhǔn)庫(kù)<new>中的分類(lèi),plain new和nothrow new是replaceable,用戶可以通過(guò)重載操作符來(lái)實(shí)現(xiàn)自定義的內(nèi)存管理,placement new是不可重載的操作。
- plain new和nothrow new執(zhí)行的動(dòng)作如下:
1.分配空間
2.初始化對(duì)象
比如說(shuō),我們執(zhí)行Complex *p = new Complex(1, 2);等價(jià)于如下代碼:
void *mem = operator new(sizeof(Complex)); //分配空間
pc = static_cast<Complex *>men; //類(lèi)型轉(zhuǎn)換
pc->Complex::Complex(1,2); //初始化對(duì)象
- placement new執(zhí)行的動(dòng)作如下:
1初始化對(duì)象
operator new并沒(méi)有進(jìn)行內(nèi)存空間的分配,只是返回之前分配的空間指針。在<new>頭文件中,有一份placement new的默認(rèn)實(shí)現(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++刪除動(dòng)態(tài)分配的Heap內(nèi)存使用delete。delete的用法也和new一樣存在三種且一一對(duì)應(yīng)。
聲明:
//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依然有三類(lèi)聲明,但是只有plain delete可以通過(guò)delete-expression的方式使用。nothrow delete和placement delete只在相應(yīng)的new-expression拋出異常時(shí)由系統(tǒng)自動(dòng)調(diào)用。如果想手動(dòng)使用nothrow delete和placement delete可以通過(guò)函數(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要配對(duì)使用,如果不配對(duì)使用可能出現(xiàn)內(nèi)存泄漏。出現(xiàn)內(nèi)存泄漏的原因并不是因?yàn)閐elete不能完全釋放array new分配的內(nèi)存,而是因?yàn)槭褂胐elete釋放array new分配的內(nèi)存時(shí),編譯器只對(duì)array new分配的數(shù)組的某一個(gè)對(duì)調(diào)用析構(gòu)函數(shù)。這樣就會(huì)導(dǎo)致數(shù)組中的其他對(duì)象因?yàn)闆](méi)有調(diào)用析構(gòu)函數(shù)而導(dǎo)致內(nèi)存沒(méi)有正確釋放。
下面通過(guò)一個(gè)例子來(lái)說(shuō)明這個(gè)問(wèn)題。
代碼:
void ArrayNewAndArrayDeleteTest()
{
cout << "ArrayNewAndDelete:" << endl;
String *p1 = new String[3];
delete p1;
cout << "ArrayNewAndArrayDelete:" << endl;
String *p2 = new String[3];
delete[] p2;
}
運(yùn)行結(jié)果:
****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 ****
從結(jié)果可以看出,
- 使用array new分配數(shù)組p1時(shí)調(diào)用了3次構(gòu)造函數(shù)(3個(gè)normal ctor輸出),使用delete釋放內(nèi)存時(shí)只調(diào)用了一次析構(gòu)函數(shù)(只有一次dtor輸出);
- 使用array new分配數(shù)組p2時(shí)調(diào)用了3次構(gòu)造函數(shù)(3個(gè)normal ctor輸出),使用array delete釋放內(nèi)存時(shí)調(diào)用了3次析構(gòu)函數(shù)(3個(gè)dtor輸出)
綜上,array new配合delete,會(huì)導(dǎo)致析構(gòu)函數(shù)只調(diào)用一次,從而導(dǎo)致內(nèi)存泄漏。
3.其他
編譯器按照數(shù)據(jù)成員的聲明順序初始化對(duì)象,而不是按照初始化列表聲明的順序初始化對(duì)象。構(gòu)造函數(shù)的初始化列表最好和類(lèi)的數(shù)據(jù)成員的聲明順序一致,且最好不要使用類(lèi)的其他成員來(lái)初始化類(lèi)的成員。
說(shuō)明:
1.本文使用的String對(duì)象為侯老師上課所用String class,只是對(duì)函數(shù)的調(diào)用添加輸出語(yǔ)句。
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