C++語言11標準中的智能指針詳解ios
1.智能指針的做用
C++程序設計中使用堆內存是很是頻繁的操做,堆內存的申請和釋放都由程序員本身管理。程序員本身管理堆內存能夠提升了程序的效率,可是總體來講堆內存的管理是麻煩的,C++11中引入了智能指針的概念,方便管理堆內存。使用普通指針,容易形成堆內存泄露(忘記釋放),二次釋放,程序發生異常時內存泄露等問題等,使用智能指針能更好的管理堆內存。
理解智能指針須要從下面三個層次:
一、從較淺的層面看,智能指針是利用了一種叫作RAII(資源獲取即初始化)的技術對普通的指針進行封裝,這使得智能指針實質是一個對象,行爲表現的卻像一個指針。
二、智能指針的做用是防止忘記調用delete釋放內存和程序異常的進入catch塊忘記釋放內存。另外指針的釋放時機也是很是有考究的,屢次釋放同一個指針會形成程序崩潰,這些均可以經過智能指針來解決。
三、智能指針還有一個做用是把值語義轉換成引用語義。C++和Java有一處最大的區別在於語義不一樣,在Java裏面下列代碼:
Animal a = new Animal();
Animal b = a;
你固然知道,這裏其實只生成了一個對象,a和b僅僅是把持對象的引用而已。但在C++中不是這樣,
Animal a;
Animal b = a;
這裏倒是就是生成了兩個對象。
2.智能指針的使用
智能指針在C++11版本以後提供,包含在頭文件<memory>中,shared_ptr、unique_ptr、weak_ptr
程序員
2.1 shared_ptr的使用
shared_ptr多個指針指向相同的對象。shared_ptr使用引用計數,每個shared_ptr的拷貝都指向相同的內存。每使用他一次,內部的引用計數加1,每析構一次,內部的引用計數減1,減爲0時,自動刪除所指向的堆內存。shared_ptr內部的引用計數是線程安全的,可是對象的讀取須要加鎖。
初始化。智能指針是個模板類,能夠指定類型,傳入指針經過構造函數初始化。也可使用make_shared函數初始化。不能將指針直接賦值給一個智能指針,一個是類,一個是指針。例如std::shared_ptr<int> p4 = new int(1);的寫法是錯誤的拷貝和賦值。拷貝使得對象的引用計數增長1,賦值使得原對象引用計數減1,當計數爲0時,自動釋放內存。後來指向的對象引用計數加1,指向後來的對象。
get函數獲取原始指針:注意不要用一個原始指針初始化多個shared_ptr,不然會形成二次釋放同一內存
?注意避免循環引用,shared_ptr的一個最大的陷阱是循環引用,循環,循環引用會致使堆內存沒法正確釋放,致使內存泄漏。循環引用在weak_ptr中介紹。
編程
// 編譯工具:Visual Studio 2013(取消SDL檢查)安全
#include "stdafx.h"
#include <iostream>
#include <memory>
ide
int _tmain(int argc, _TCHAR* argv[])
{
int a = 10;
std::shared_ptr<int> ptra = std::make_shared<int>(a);
std::shared_ptr<int> ptra2(ptra); //copy
std::cout << ptra.use_count() << std::endl;
函數
int b = 20; int *pb = &a; //std::shared_ptr<int> ptrb = pb; //error std::shared_ptr<int> ptrb = std::make_shared<int>(b); ptra2 = ptrb; //assign pb = ptrb.get(); //獲取原始指針 std::cout << ptra.use_count() << std::endl; std::cout << ptrb.use_count() << std::endl; return 0;
}工具
2.2 unique_ptr的使用
unique_ptr「惟一」擁有其所指對象,同一時刻只能有一個unique_ptr指向給定對象(經過禁止拷貝語義、只有移動語義來實現)。相比與原始指針unique_ptr用於其RAII的特性,使得在出現異常的狀況下,動態資源能獲得釋放。unique_ptr指針自己的生命週期:從unique_ptr指針建立時開始,直到離開做用域。離開做用域時,若其指向對象,則將其所指對象銷燬(默認使用delete操做符,用戶可指定其餘操做)。unique_ptr指針與其所指對象的關係:在智能指針生命週期內,能夠改變智能指針所指對象,如建立智能指針時經過構造函數指定、經過reset方法從新指定、經過release方法釋放全部權、經過移動語義轉移全部權。
// 編譯工具:Visual Studio 2013(取消SDL檢查)
this
#include "stdafx.h"
#include <iostream>
#include <memory>
線程
int _tmain(int argc, _TCHAR* argv[])
{
std::unique_ptr<int> uptr(new int(10)); //綁定動態對象
//std::unique_ptr<int> uptr2 = uptr; //不能賦值
//std::unique_ptr<int> uptr2(uptr); //不能拷貝
std::unique_ptr<int> uptr2 = std::move(uptr); //轉換全部權
uptr2.release(); //釋放全部權
設計
//超過uptr的做用域,內存釋放 return 0;
}
2.3 weak_ptr的使用
weak_ptr是爲了配合shared_ptr而引入的一種智能指針,由於它不具備普通指針的行爲,沒有重載operator*和->,它的最大做用在於協助shared_ptr工做,像旁觀者那樣觀測資源的使用狀況。weak_ptr能夠從一個shared_ptr或者另外一個weak_ptr對象構造,得到資源的觀測權。但weak_ptr沒有共享資源,它的構造不會引發指針引用計數的增長。使用weak_ptr的成員函數use_count()能夠觀測資源的引用計數,另外一個成員函數expired()的功能等價於use_count()==0,但更快,表示被觀測的資源(也就是shared_ptr的管理的資源)已經不復存在。weak_ptr可使用一個很是重要的成員函數lock()從被觀測的shared_ptr得到一個可用的shared_ptr對象, 從而操做資源。但當expired()==true的時候,lock()函數將返回一個存儲空指針的shared_ptr。
// 編譯工具:Visual Studio 2013(取消SDL檢查)
#include "stdafx.h"
#include <iostream>
#include <memory>
int _tmain(int argc, _TCHAR* argv[])
{
std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
std::cout << sh_ptr.use_count() << std::endl;
std::weak_ptr<int> wp(sh_ptr); std::cout << wp.use_count() << std::endl; if (!wp.expired()){ std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr *sh_ptr = 100; std::cout << wp.use_count() << std::endl; } //delete memory return 0;
}
2.4 循環引用
考慮一個簡單的對象建模——家長與子女:a Parent has a Child, a Child knowshis/her Parent。在Java 裏邊很好寫,不用擔憂內存泄漏,也不用擔憂空懸指針,只要正確初始化myChild 和myParent,那麼Java 程序員就不用擔憂出現訪問錯誤。一個handle 是否有效,只須要判斷其是否non null。
public class Parent
{
private Child myChild;
}
public class Child
{
private Parent myParent;
}
在C++ 裏邊就要爲資源管理費一番腦筋。若是使用原始指針做爲成員,Child和Parent由誰釋放?那麼如何保證指針的有效性?如何防止出現空懸指針?這些問題是C++面向對象編程麻煩的問題,如今能夠藉助smart pointer把對象語義(pointer)轉變爲值(value)語義,shared_ptr輕鬆解決生命週期的問題,沒必要擔憂空懸指針。可是這個模型存在循環引用的問題,注意其中一個指針應該爲weak_ptr。
【原始指針的作法,容易出錯】
// 編譯工具:Visual Studio 2013(取消SDL檢查)
#include "stdafx.h"
#include <iostream>
#include <memory>
class Child;
class Parent;
class Parent {
private:
Child myChild;
public:
void setChild(Child
ch) {
this->myChild = ch;
}
void doSomething() { if (this->myChild) { } } ~Parent() { delete myChild; }
};
class Child {
private:
Parent myParent;
public:
void setPartent(Parent
p) {
this->myParent = p;
}
void doSomething() {
if (this->myParent) {
} } ~Child() { delete myParent; }
};
int main() {
{
Parent p = new Parent;
Child
c = new Child;
p->setChild(c);
c->setPartent(p);
delete c; //only delete one
}
return 0;
}
【循環引用內存泄露的問題】
// 編譯工具:Visual Studio 2013(取消SDL檢查)
#include "stdafx.h"
#include <iostream>
#include <memory>
class Child;
class Parent;
class Parent {
private:
std::shared_ptr<Child> ChildPtr;
public:
void setChild(std::shared_ptr<Child> child) {
this->ChildPtr = child;
}
void doSomething() { if (this->ChildPtr.use_count()) { } } ~Parent() { }
};
class Child {
private:
std::shared_ptr<Parent> ParentPtr;
public:
void setPartent(std::shared_ptr<Parent> parent) {
this->ParentPtr = parent;
}
void doSomething() {
if (this->ParentPtr.use_count()) {
} } ~Child() { }
};
int main() {
std::weak_ptr<Parent> wpp;
std::weak_ptr<Child> wpc;
{
std::shared_ptr<Parent> p(new Parent);
std::shared_ptr<Child> c(new Child);
p->setChild(c);
c->setPartent(p);
wpp = p;
wpc = c;
std::cout << p.use_count() << std::endl; // 2
std::cout << c.use_count() << std::endl; // 2
}
std::cout << wpp.use_count() << std::endl; // 1
std::cout << wpc.use_count() << std::endl; // 1
return 0;
}
【正確的作法】
// 編譯工具:Visual Studio 2013(取消SDL檢查)
#include "stdafx.h"
#include <iostream>
#include <memory>
class Child;
class Parent;
class Parent {
private:
//std::shared_ptr<Child> ChildPtr;
std::weak_ptr<Child> ChildPtr;
public:
void setChild(std::shared_ptr<Child> child) {
this->ChildPtr = child;
}
void doSomething() { //new shared_ptr if (this->ChildPtr.lock()) { } } ~Parent() { }
};
class Child {
private:
std::shared_ptr<Parent> ParentPtr;
public:
void setPartent(std::shared_ptr<Parent> parent) {
this->ParentPtr = parent;
}
void doSomething() {
if (this->ParentPtr.use_count()) {
} } ~Child() { }
};
int main() {
std::weak_ptr<Parent> wpp;
std::weak_ptr<Child> wpc;
{
std::shared_ptr<Parent> p(new Parent);
std::shared_ptr<Child> c(new Child);
p->setChild(c);
c->setPartent(p);
wpp = p;
wpc = c;
std::cout << p.use_count() << std::endl; // 2
std::cout << c.use_count() << std::endl; // 1
}
std::cout << wpp.use_count() << std::endl; // 0
std::cout << wpc.use_count() << std::endl; // 0
return 0;
}
3.智能指針的設計和實現
下面是一個簡單智能指針的demo。智能指針類將一個計數器與類指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。每次建立類的新對象時,初始化指針並將引用計數置爲1;當對象做爲另外一對象的副本而建立時,拷貝構造函數拷貝指針並增長與之相應的引用計數;對一個對象進行賦值時,賦值操做符減小左操做數所指對象的引用計數(若是引用計數爲減至0,則刪除對象),並增長右操做數所指對象的引用計數;調用析構函數時,構造函數減小引用計數(若是引用計數減至0,則刪除基礎對象)。智能指針就是模擬指針動做的類。全部的智能指針都會重載 -> 和 * 操做符。智能指針還有許多其餘功能,比較有用的是自動銷燬。這主要是利用棧對象的有限做用域以及臨時對象(有限做用域實現)析構函數釋放內存。
// 編譯工具:Visual Studio 2013(取消SDL檢查)
#include "stdafx.h"
#include <iostream>
#include <memory>
template<typename T>
class SmartPointer {
private:
T _ptr;
size_t
_count;
public:
SmartPointer(T* ptr = nullptr) :
_ptr(ptr) {
if (_ptr) {
_count = new size_t(1);
}
else {
_count = new size_t(0);
}
}
SmartPointer(const SmartPointer& ptr) { if (this != &ptr) { this->_ptr = ptr._ptr; this->_count = ptr._count; (*this->_count)++; } } SmartPointer& operator=(const SmartPointer& ptr) { if (this->_ptr == ptr._ptr) { return *this; } if (this->_ptr) { (*this->_count)--; if (this->_count == 0) { delete this->_ptr; delete this->_count; } } this->_ptr = ptr._ptr; this->_count = ptr._count; (*this->_count)++; return *this; } T& operator*() { assert(this->_ptr == nullptr); return *(this->_ptr); } T* operator->() { assert(this->_ptr == nullptr); return this->_ptr; } ~SmartPointer() { (*this->_count)--; if (*this->_count == 0) { delete this->_ptr; delete this->_count; } } size_t use_count(){ return *this->_count; }
};
int main() {
{
SmartPointer<int> sp(new int(10));
SmartPointer<int> sp2(sp);
SmartPointer<int> sp3(new int(20));
sp2 = sp3;
std::cout << sp.use_count() << std::endl;
std::cout << sp3.use_count() << std::endl;
}
//delete operator
}