c++ primer(第五版)筆記 第十二章 動態內存(1)

// 靜態內存(static) 局部static, 類static, 定義在任何函數之外的變量,由編譯器自動創建和銷燬,程序使用前創建,程序結束時銷燬,
// 棧(stack)		函數內的非static 對象,由編譯器自動創建和銷燬,僅在其定義的程序塊運行時存在
// 堆(heap)			動態分配的對象,運行時分配的對象,程序顯式的銷燬

// 動態內存的管理
	// new		在動態內存中爲對象分配空間並返回指向該對象的指針,可選擇進行初始化
	// delete	接受動態對象的指針,銷燬該對象,釋放與之關聯的空間

#include<memory>	
#include<iostream>	
#include<string>	
#include<initializer_list>	
#include<vector>	
#include<stdexcept>	
#include<new>	

using std::shared_ptr;
using std::unique_ptr;
using std::make_shared;
using std::cout;
using std::cin;
using std::endl;
using std::string;
using std::initializer_list;
using std::vector;
using std::out_of_range;
using std::nothrow;

// 對象間共享數據類定義
class StrBlob{
public:
	typedef vector< string>::size_type size_type;
	// 構造函數
	StrBlob();
	StrBlob( initializer_list< string> il);
	// 普通成員函數
	size_type size() const {
		return data->size();
	}
	size_type count() const {
		return data.use_count();
	}
	bool empty() const {
		return data->empty();
	}
	// 添加刪除
	void push_back( const string &s){
		data->push_back( s);
	}
	void pop_back();
	// 訪問
	string& front() const;
	string& back() const;
	
private:
	// 數據
	shared_ptr< vector< string>> data;
	// 檢查 data[i] 的合法,拋出異常	
	void check( size_type i, const string &s) const;
};

// 構造函數
StrBlob::StrBlob() : data( make_shared< vector< string>>()){}
StrBlob::StrBlob( initializer_list< string> il) : 
				data( make_shared< vector< string>>( il)){}
				
// 檢查 data[i] 的合法,拋出異常		
void StrBlob::check( size_type i, const string &s) const{
	if( i >= data->size())
		throw out_of_range( s);
}
// 刪除
void StrBlob::pop_back(){
	check(0, "pop_back on empty strBlob");
	data->pop_back();
}
// 訪問
string& StrBlob::front() const {
	check(0, "front on empty strBlob");
	data->front();
}
string& StrBlob::back() const {
	check(0, "back on empty strBlob");
	data->back();
}
// 返回動態內存
vector< int> *get_pvi(){
	return new vector< int>();
}

void save_pvi(vector< int> * pvi){
	int n = 0;
	while( cin >> n)
		pvi->push_back( n);
}
void print_pvi(vector< int> * pvi){
	for( auto &w : *pvi)
		cout << w << endl;
}

int main(){
// 智能指針(smart pointer),定義於 memory.h
	// shared_ptr	允許多個指針指向同一個對象	
	// unique_ptr	獨佔所指向的對象
	// week_ptr		弱引用,指向 shared_ptr 所管理的對象
	
	// shared_ptr unique_ptr 都支持的操作
	// 1.創建時必須指明一個指針可以指向的類型,默認初始化爲空指針
	shared_ptr< string> sp1, sp2;
	unique_ptr< string> up;
	// 2.用作判斷條件,若指向一個對象,則爲 true
	if( sp1 && sp1->empty()){	//4.調用指向對象的成員
		// 3.解引用,獲得指向對象的左值
		*sp1 = "hello";
		// 6.返回智能指針中保存的指針
		cout << *( sp1.get()) << endl;
	}
	// 5.交換兩個指針
	swap( sp1, sp2);
	sp1.swap( sp2);
	
	
	// shared_ptr 獨有的操作
	// 1> make_shared 返回一個 shared_ptr,指向一個動態分配的類型爲 string的對象,並初始化爲 world
	// 最安全的分配和使用動態內存的方法,初始化的參數必須是指定類型的構造函數的參數
	sp2 = make_shared< string>( "world");
	// 每個shared_ptr 都有一個引用計數(reference count),當拷貝時,計數器增加,賦值或銷燬時,計數器減少,當變爲0時,自動釋放所管理的對象
	// 2> sp3是 sp2 一個拷貝,同時增加 sp2的計數器,sp2 的指針必須能轉換爲 string *
	shared_ptr< string> sp3( sp2);
	// 3> sp1 和 sp3 所保存的指針必須能夠相互轉換,同時遞減sp1計數,若爲0,則釋放其管理的內存,遞增 sp3 的計數
	sp1 = sp3;
	// 4> 如果 use_count() 爲1, 則unique() 爲true,否則爲 false
	if( sp3.unique())
		cout << "count == 1" << endl;
	else
		// use_count() 返回與 sp3 共享對象的智能指針數量,比較慢,主要用於測試
		cout << "count : " << sp3.use_count() << endl;
		
	// 使用動態內存的情況
		// 不知道要使用多少對象
		// 不知道對象的確切類型
		// 在多個對象之間共享數據
	StrBlob sb1, sb2{"hello", "world"};	
	cout << "sb1.count = " << sb1.size() << endl;
	cout << "sb2.count = " << sb2.size() << endl;
	{
		StrBlob sb3{"test"};
		sb1 = sb3;
		cout << "sb3.count = " << sb3.size() << endl;
		cout << "sb1.count = " << sb1.size() << endl;
		sb3.push_back( "strblob");
		cout << "sb3.count = " << sb3.size() << endl;
		cout << "sb1.count = " << sb1.size() << endl;
	}
	const StrBlob sb4{ "say", "yeah!~"};
	cout << sb4.front() << " < > " << sb4.back() << endl;
	cout << sb2.front() << " < > " << sb2.back() << endl;
	cout << "sb1.count = " << sb1.size() << endl;
	cout << "sb2.count = " << sb2.size() << endl;
	sb1 = sb2;
	cout << "sb1.count = " << sb1.size() << endl;
	cout << "sb2.count = " << sb2.size() << endl;
	
	// 直接管理內存 new 分配內存,delete 釋放 new 分配的內存
	// 注意區別
	int *pi1 = new int;			// 默認初始化,對於內置類型,其值是未定義的
	int *pi2 = new int();		// 值初始化
	auto *pi3 = new auto(5);	// 自動推斷類型
	const int *pi4 = new const int(1024);	// const 對象必須初始化,如果類類型有默認構造函數可以隱式初始化
	
	cout << *pi1 << " - " << *pi2 << " - " << *pi3 << " - " << *pi4 << endl;
	
	// 如果內存耗盡,new 表達式失敗,會拋出 bad_alloc 異常
	// bad_alloc nothrow new 定義於 new.h
	int *pi5 = new (nothrow) int();	// 定位 new (placement new),如果分配失敗,不會拋出異常,返回一個空指針
	
	// 釋放動態內存 (delete expression)
	// 釋放一塊並非 new 分配的內存或將相同的指針釋放多次,其行爲是未定義的
	delete pi5;	// 兩步,銷燬指向的對象,釋放對應內存,pi5必須是指向動態分配的內存或是一個空指針
	
	// 動態內存有效期直到顯式釋放
	// 如果使用了返回動態內存指針的函數,使用者使用完必須顯式釋放
	// 常見問題:
		// 1.忘記 delete 分配的內存,很難檢測,多數情況下,等內存耗盡才發現
		// 2.使用已經釋放掉的對象,雖然指針已失效但多數情況下仍保存了原地址,類似於未定義的指針,建議置爲空指針,
		// 	但是如果有多個指針指向該內存,逐個釋放也很麻煩
		// 3.釋放已經釋放過的內存,未定義行爲
	auto pvi = get_pvi();
	save_pvi( pvi);
	print_pvi( pvi);
	delete pvi;
	
	// 智能指針的構造函數是 explicit, 不能進行隱式轉換,只能使用直接初始化
	// 錯誤:shared_ptr< string> ps1 = new string("hello world");
	shared_ptr< string> ps2( new string("hello world"));	
	
	// 定義改變 shared_ptr 的其他方法
		// shared_ptr< T> p( q)  	p 管理 q指向的對象,q必須指向 new 分配的內存,且能夠轉換爲 T* 類型
		// shared_ptr< T> p( u)		p 從 unique_ptr u 那裏接管了對象的所有權,將 u 置空
		// shared_ptr< T> p( q, d)	p 從 內置類型指針 q 那裏接管了對象的所有權,q 必須能轉換爲 T*, p 將使用可調用對象 d 代替 delete
		// shared_ptr< T> p( p2, d)	p 是 shared_ptr p2 的拷貝, 但 p 可以使用可調用對象 d 代替 delete
		// p.reset()				若 p 是唯一指向其對象的 shared_ptr,reset會釋放此對象
		// p.reset( q)				若同時傳遞了可選的內置指針 q,會另 p 指向 q, 否則爲空
		// p.reset( q, d)			若同時還傳遞了 d,使用可調用對象 d 代替 delete
		
	// 不要使用 get() 初始化另一個智能指針或爲智能指針賦值
	shared_ptr< int> spi1( new int( 99));
	// pi1 和 spi1 指向同一個內存
	int *pi1 = spi1.get();
	{
	// pi1 spi2 和 spi1 指向同一個內存,但分別是2個不同的智能指針,且計數都爲1,當spi2 脫離作用域,其指向的內存會被釋放,後續的解引用行爲未定義
		shared_ptr< int> ( spi2);
	}
	int vspi1 = *spi1;	//未定義行爲
	
	// 智能指針使用規範:
		// 不使用相同的內置指針初始化多個智能指針
		// 不 delete get() 返回的指針
		// 不使用 get() 初始化或reset 另一個智能指針
		// 如果使用了 get() 指針,當其最後一個對應的智能指針銷燬後,指針變爲無效
		// 如果使用的智能指針管理的資源不是 new 分配的內存,記住傳遞一個刪除器
	
	
	// unique_ptr
		
	// 定義改變 unique_ptr 的其他方法
		// unique_ptr< T> p  	指向 T 類型的對象, 空 unique_ptr, 可以使用 delete 釋放
		// unique_ptr< T, D> p		同上,使用 D 類型的可調用對象來釋放指針
		// unique_ptr< T, D> p ( d)		同上,使用 D 類型的可調用對象 d來釋放指針
		// p = nullptr				釋放 p 指向的對象,將 p 置空
		// p.release()					放棄控制權,返回指針,將 p 置空
		// p.reset()				釋放此對象
		// p.reset( q)				若同時傳遞了可選的內置指針 q,會另 p 指向 q, 否則爲空
		// p.reset( nullptr)			
		
		// 不支持賦值和拷貝, 可以通過 release() 或 reset() 轉移控制權
		unique< int> upi1;
		unique< int> upi2 ( new int ( 99));
		upi1.reset( upi2.release());
		
	// weak_ptr
		// 指向 shared_ptr 關聯的對象,但不增加其引用計數,當對象最後一個 shared_ptr 被銷燬,即使有 weak_ptr 仍會銷燬
		
		// weak_ptr< T> w		空 weak_ptr 指向類型 T 的對象
		// weak_ptr< T> w( sp)	與 shared_ptr sp 指向同一個對象, T 必須能轉換爲 sp 指向的類型
		// w = p				p 可以是 shared_ptr	或 weak_ptr,賦值後共享對象
		// w.reset()			置空 w
		// w.use_count()		與 w 共享對象的 shared_ptr 數量
		// w.expired()			若 w.use_count() 爲0,返回 true,否則爲 false
		// w.lock()				如果 w.expired() 爲真,返回空 shared_ptr, 否則返回指向 w 對象的 shared_ptr
		
	return 1;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章