C++中對於動態內存的使用非常嚴格,一次申請必須對應一次釋放,否則將造成內存泄漏。這也要求程序員格外小心,比如如下示例:
void getStr() {
std::string * pstr = new std::string();//pstr爲局部變量
*pstr = "Hello world";
....
return;
}
當該方法執行完畢後,局部變量pstr所佔用內存自動釋放,但其指向的內存則一直駐留。必須通過delete pstr來釋放,因此這裏將造成內存泄漏。
還有一種情況就是當程序因異常而終止時:
#include <stdexcept>
void getStr() {
char * pch = new char[1024];
...
if (true)
throw logic_error("throw a exception");
delete [] pch;
}
雖然在這裏沒有忘記delete釋放內存,但是當程序執行到throw時,將不再執行throw之後的語句,因此導致內存泄漏。針對於這個例子,雖然可以通過如下的方式避免這個問題,但依舊需要非常小心:
#include <stdexcept>
void getStr() {
char * pch = new char[1024];
...
if (true)
try{
throw logic_error("throw a exception");
} catch(...){
delete [] pch;
throw;
}
delete [] pch;
}
爲了讓程序員不再因內存泄漏而操心,C++11中提供了幾個用於動態內存管理的智能指針。
智能指針的原理
如果在指針對象過期時,讓它的析構函數刪除它指向的內存,那不就避免內存的泄漏了嗎?比如當自動變量pstr被銷燬時,讓他的析構函數可以釋放它指向的內。
智能指針正是採用這種方式實現的,在智能指針類中,定義了類似指針的對象,然後將new獲得的地址賦給這個對象,當智能指針過期後,其析構函數將使用delete來釋放這塊內存。
使用智能指針
智能指針模板類位於頭文件memory中,因此使用時必須包含該頭文件。memory中提供了四種智能指針模板類:auto_ptr,unique_ptr,shared_ptr,weak_ptr,其中auto_ptr在C++11中被棄用,在C++17中被移除,因此主要學習其餘三種。
unique_ptr
unique_ptr類模板如下:
template<class T, class Deleter = std::default_delete<T>>
class unique_ptr;
template <class T,class Deleter>
class unique_ptr<T[], Deleter>;
因此,unique_ptr有兩個版本:
- 管理個對象(以 new 分配)
- 管理動態分配的對象數組(以 new[] 分配)
構造方法
unique_ptr的構造方法有多個,但常用如下幾個個:
constexpr unique_ptr() noexcept;
constexpr unique_ptr (nullptr_t) noexcept : unique_ptr() {}
explicit unique_ptr(pointer p) noexcept;
unique_ptr通過建立所有權概念來管理對象,也就是說,對於特定的對象,只能有一個智能指針可以擁有它。因此:
- 不能使用一個unique_ptr對象來初始化另一個unique_ptr對象,其拷貝構造不可用:unique_ptr (const unique_ptr&) = delete;
- 不能使用一個unique_ptr對象來給另一個unique_ptr賦值,其賦值運算符不可用:unique_ptr& operator= (const unique_ptr&) = delete.
然而,當試圖將一個unique_ptr對象賦給另一個unique_ptr時,如果源unique_ptr是一個臨時右值,則編譯器允許這樣做,如:
std::unique_ptr<Person> get_uptr(Person* p) {
std::unique_ptr<Person> temp (p);
return temp;
}
std::unique_ptr<Person> uptr4 = get_uptr(new Person("XiaoWang"));
這是通過移動構造來區分的。
如果需要將一個unique_ptr對象賦給另一個unique_ptr,則使用std::move()函數;
成員函數
- get():返回指向被管理對象的指針。
- reset():替換被管理對象;
- release(): 返回一個指向被管理對象的指針,並釋放所有權.
- swap():替換被管理對象;
- operator bool:檢查是否有關聯的被管理對象.
下面爲使用unique_ptr示例:
#include <iostream>
#include <memory>
#include <string>
class Person
{
private:
std::string name;
public:
Person(const std::string& name):name(name){
std::cout << "Person " << name << " created." << std::endl;
}
void show() const {
std::cout << "Name: " << name << std::endl;
}
~Person(){
std::cout << "Person " << name << " deleted." << std::endl;
}
};
int main()
{
// 1.創建unique_ptr管理Person*對象
Person* p = new Person("XiaoQiang");
std::unique_ptr<Person> uptr1;
std::unique_ptr<Person> uptr2(new Person("Zhangsan"));
// 2.不能用一個unique_ptr來初始化另一個unique_ptr
//拷貝構造不可用:unique_ptr (const unique_ptr&) = delete
//std::unique_ptr<Person> uptr3 = uptr2;
// 3.同一對象只能被一個unique_ptr擁有,否則編譯出錯
//賦值運算符不可用:operator=(const unique_ptr& ) = delete;
//uptr1 = uptr2;
// 3.get()返回管理對象的指針
Person* pt = uptr2.get();
pt->show();
// 4.reset()替換管理對象,原來對象將銷燬
uptr2.reset(p);
uptr2.get()->show();
// 5.release()返回管理對象,並釋放所有權
uptr2.release()->show();
// 6.operator bool 判斷是否有關聯對象
if(uptr2)
uptr2->show();
else
std::cout << "uprt is null" << std::endl;
// 7.swap()交換管理對象
std::unique_ptr<Person> uptr3(new Person("LiSi"));
std::unique_ptr<Person> uptr4(new Person("James"));
uptr3.swap(uptr4);
uptr3->show();
// 8.std::move()函數將一個unique_ptr對象賦給另一個unique_ptr對象
uptr1 = std::move(uptr4);
uptr1->show();
// 9.c++14中,使用std::make_unique(T&& t)創建unique_ptr,參數爲右值引用
auto uptr5 = std::make_unique<Person>("LaoWang");
uptr5->show();
return 0;
}
/*
@ubuntu:~$ g++ uptr.cpp -o uptr --std=c++14
@ubuntu:~$ ./uptr
Person XiaoQiang created.
Person Zhangsan created.
Name: Zhangsan
Person Zhangsan deleted.
Name: XiaoQiang
Name: XiaoQiang
uprt is null
Person LiSi created.
Person James created.
Name: James
Name: LiSi
Person LaoWang created.
Name: LaoWang
Person LaoWang deleted.
Person James deleted.
Person LiSi deleted.
*/
shared_ptr
shared_ptr通過引用計數機制來管理對象,多個 shared_ptr 對象可佔有同一對象。可以通過成員函數use_count()返回shared_ptr所指對象的引用計數。它的類模板如下:
template <class T> class shared_ptr;
構造方法
其常用構造方法如下:
constexpr shared_ptr() noexcept;
constexpr shared_ptr(nullptr_t) : shared_ptr() {}
template <class U> explicit shared_ptr (U* p);
shared_ptr (const shared_ptr& x) noexcept;
template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;
template <class U> explicit shared_ptr (const weak_ptr<U>& x);
template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);
成員方法
- get():返回管理對象的指針;
- swap():交換管理對象;
- reset():替換管理對象,原對象將銷燬;
- use_count():返回shared_ptr所指對象的引用計數;
- unique():判斷所管理對象是否僅由當前shared_ptr的實例管理;
- operator bool:檢查是否有關聯的管理對象;
下面爲使用shared_ptr的示例:
#include <iostream>
#include <memory>
#include <string>
int main()
{
std::string* str = new std::string("I'm shared_ptr.");
// 1.創建shared_ptr
std::shared_ptr<std::string> sptr1;
std::shared_ptr<std::string> sptr2(str);
sptr1 = sptr2;
std::shared_ptr<std::string> sptr3(new std::string("Zhangsan"));
// 2.使用右值unique_ptr創建shared_ptr
std::shared_ptr<std::string> sptr4(std::unique_ptr<std::string>(
new std::string("I'm unique_ptr.")));
std::cout << *sptr4 << std::endl;
// 3.get()返回管理對象的指針
std::string* pstr = sptr1.get();
std::cout << "*pstr: " << *pstr << std::endl;
// 4.use_count()返回引用計數
std::cout << "sptr1.use_count: " << sptr1.use_count() << std::endl;
std::cout << "sptr2.use_count: " << sptr2.use_count() << std::endl;
std::cout << "sptr3.use_count: " << sptr3.use_count() << std::endl;
// 5.unique()檢查是否僅僅被該對象關聯
std::cout << "sptr1.unique: " << sptr1.unique() << std::endl;
std::cout << "sptr3.unique: " << sptr3.unique() << std::endl;
// 6.operator bool檢查是否有關聯的對象
if(sptr1)
std::cout << *sptr1 << std::endl;
// 7.reset()替換管理對象
sptr1.reset(new std::string("I'm new shared_ptr."));
std::cout << *sptr1 << std::endl;
// 8.swap()交換管理對象
sptr1.swap(sptr3);
std::cout << *sptr1 << std::endl;
// 9.使用std::make_shared(T&& t)創建shared_ptr.
auto sptr5 = std::make_shared<std::string>("I'm from std::makeA_shared.");
std::cout << *sptr5 << std::endl;
return 0;
}
/*
@ubuntu:~$ g++ shrptr.cpp -o shrptr --std=c++11
@ubuntu:~$ ./shrptr
I'm unique_ptr.
*pstr: I'm shared_ptr.
sptr1.use_count: 2
sptr2.use_count: 2
sptr3.use_count: 1
sptr1.unique: 0
sptr3.unique: 1
I'm shared_ptr.
I'm new shared_ptr.
Zhangsan
I'm from std::makeA_shared.
*/
weak_ptr
weak_ptr是一種不控制所管理對象生存期的智能指針,也就是說,它對被管理對象具有弱引用性。它指向一個由shared_ptr管理的對象,並且將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數,一旦最後指向被管理對象的shared_ptr被銷燬,即使有weak_ptr指向對象,對象依舊被銷燬釋放。weak_ptr在訪問所引用的對象前必須先轉換爲std::shared_ptr。
構造方法
weak_ptr構造函數如下:
constexpr weak_ptr() noexcept;
template<class Y>
weak_ptr(shared_ptr<Y> const& r) noexcept;
weak_ptr(weak_ptr const& r) noexcept;
template<class Y>
weak_ptr(weak_ptr<Y> const& r) noexcept;
weak_ptr(weak_ptr&& r) noexcept; // C++14
template<class Y>
weak_ptr(weak_ptr<Y>&& r) noexcept; // C++14
成員函數
- use_count():返回weak_ptr所指向對象的引用計數;
- expired():檢查``weak_ptr是否過期,若被管理對象已被刪除則爲true,否則爲false,等價於use_count() = 0`;
- lock():創建新的shared_ptr對象,它共享被管理對象的所有權。若無被管理對象,即 *this 爲空,則返回亦爲空的shared_ptr.
- reset():釋放被管理對象的所有權;
- swap(weak_ptr& r):交換*this與r的內容;
使用weak_ptr示例如下:
#include <iostream>
#include <memory>
#include <string>
class Person
{
private:
std::string name;
public:
Person(const std::string& str):name(str){}
~Person(){}
void show() const {
std::cout << "Name: " << name << std::endl;
}
};
int main()
{
// 1.創建weak_ptr管理Person*
std::weak_ptr<Person> wptr1;
std::shared_ptr<Person> sptr1(new Person("Zhangsan"));
wptr1 = sptr1;
std::weak_ptr<Person> wptr2(wptr1);
std::cout << sptr1.use_count() << std::endl;
// 2.訪問所管理對象前,必須轉換爲shared_ptr.
// wptr1->show();//Error
std::shared_ptr<Person> sptr2(wptr1);
sptr2->show();
std::cout << "sptr2.use_count: " << sptr2.use_count() << std::endl;
// 3.查看wptr1所管理對象的引用計數
std::cout << "wptr1.use_count: " << wptr1.use_count() << std::endl;
// 4.檢查是否過期
std::weak_ptr<Person> wptr3;
{
auto sptr2 = std::make_shared<Person>("Lisi");
wptr3 = sptr2;
std::cout << "wptr3.expired ? " << std::boolalpha << wptr3.expired();
std::cout << ", use count: " << wptr3.use_count() << std::endl;
}
std::cout << "wptr3.expired ? " << std::boolalpha << wptr3.expired() << std::endl;
// 5.創建新的shared_ptr共享被管理對象
std::cout << "wptr1.use count before lock(): " << wptr1.use_count() << std::endl;
auto p = wptr1.lock();
std::cout << "wptr2.use count after locked(): " << wptr1.use_count() << std::endl;
p->show();
// 6.交換被管理對象
auto sptr3 = std::make_shared<Person>("XiaoQiang");
std::weak_ptr<Person> wptr4(sptr3);
wptr1.swap(wptr4);
std::cout << "wptr4.use_count: " << wptr4.use_count() << std::endl;
// 7.釋放wptr1
wptr1.reset();
std::cout << "wptr1.expired ? " << std::boolalpha << wptr1.expired() << std::endl;
return 0;
}
創建完成weak_ptr對象之後,使用之前,需要調用expired函數來判斷是否過期,如果沒有過期,才能調用函數rock來獲取share_ptr對象進行操作。
std::shared_ptr<string> share_str1(new string("hello world."));
std::cout << "share_str1.use_count()=" << share_str1.use_count() <<std::endl; //1
std::weak_ptr<string> weak_str1(share_str1);
std::cout << "weak_str1.use_count()=" << weak_str1.use_count() <<std::endl;//1
std::weak_ptr<string> weak_str2(weak_str1);
std::cout << "weak_str2.use_count()=" << weak_str2.use_count() <<std::endl;//1
//檢查是否過期,沒有的話,取出std::shared_ptr<string>進行操作
if(!weak_str2.expired())
{
std::shared_ptr<string> share_str2 = weak_str2.lock();
std::cout << "share_str2 = " << *share_str2 <<std::endl;//hello world.
}
weak_ptr的使用場景
weak_ptr最常見的用法,是和shared_ptr搭配使用,當shared_ptr用於類的成員時,如果存在循環引用,則將無法釋放內存,請看下面示例:
#include <iostream>
#include <memory>
class B;
class A
{
private:
std::shared_ptr<B> m_sptr;
public:
void set_sptr(std::shared_ptr<B>& sptr) {
m_sptr = sptr;
}
~A() { std::cout << "A deleted." << std::endl;}
};
class B
{
private:
std::shared_ptr<A> m_sptr;
public:
void set_sptr(std::shared_ptr<A>& sptr) {
m_sptr = sptr;
}
~B() { std::cout << "B deleted." << std::endl;}
};
int main()
{
A* a = new A;
B* b = new B;
std::shared_ptr<A> asptr(a);
std::shared_ptr<B> bsptr(b);
std::cout << "apstr.use_count: " << asptr.use_count() << std::endl;
std::cout << "bsptr.use_count: " << bsptr.use_count() << std::endl;
a->set_sptr(bsptr);
b->set_sptr(asptr);
std::cout << "asptr.use_count: " << asptr.use_count() << std::endl;
std::cout << "bsptr.use_count: " << bsptr.use_count() << std::endl;
return 0;
}
/*
@ubuntu:~$ g++ shaptr.cpp -o shaptr --std=c++11
@ubuntu:~$ ./shaptr
apstr.use_count: 1
bsptr.use_count: 1
asptr.use_count: 2
bsptr.use_count: 2
*/
在以上示例中,A,B類分別帶有shared_ptr的成員,並且通過set_sptr(shared_ptr&)互相共享管理對象,那麼當程序執行完畢後,asptr和bsptr將負責釋放管理對象的內存,但此時由於引用計數爲2,因此減1,從而導致new申請的A和B的內存沒有釋放,從運行結果來看,他們的析構函數未執行。
爲避免這種問題,只需要將shared_ptr成員修改爲weak_ptr即可,因爲weak_ptr對管理對象具有弱引用性,並且將一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數。修改後運行結果如下:
@ubuntu:~$ ./shaptr
apstr.use_count: 1
bsptr.use_count: 1
asptr.use_count: 1
bsptr.use_count: 1
B deleted.
A deleted.
auto_ptr(c++98的方案,cpp11已經拋棄)
採用所有權模式。
auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.”));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不會報錯.
此時不會報錯,p2剝奪了p1的所有權,但是當程序運行時訪問p1將會報錯。所以auto_ptr的缺點是:存在潛在的內存崩潰問題!