Memory (C++標準庫)
memory 是C++標準程式庫中的一個標頭檔,定義了C++標準中的智慧型指標、主記憶體分配器(allocator)、與垃圾回收有關的函式、臨時性的申請與釋放動態主記憶體的函式、在主記憶體上建立(構造)對象的函式等等。
運算子
- operator!= 測試allocator、智慧型指標是否不等
- operator==測試allocator、智慧型指標是否相等
- operator>= 測試智慧型指標所指向的資料對象位址
- operator< 測試智慧型指標所指向的資料對象位址
- operator<= 測試智慧型指標所指向的資料對象位址
- operator> 測試智慧型指標所指向的資料對象位址
- operator<< 向流對象輸出智慧型指標所指向的資料對象位址
智慧型指標
智慧型指標是指當指標對象的生命期結束解構時,同時把指標所指的對象也delete或者參照計數減1。
unique_ptr
unique_ptr是個類別模板。unique_ptr指標對象獨占式地參照所指的資料對象。不能複製構造(copy ctor),也不能複製賦值,也就是對其無法進行複製,不能得到指向同一個對象的兩個unique_ptr;但是可以移動構造和移動賦值。當unique_ptr指標對象離開其作用域,生命期結束時,自動使用內部給定的刪除器(deleter)delete所指向的資料對象。unique_ptr十分依賴於右值參照和移動語意。
template<class Type, class Del = default_delete<Type> >
class unique_ptr {
public:
typedef Type element_type; //指针基类型
typedef Del deleter_type; //析构类型
typedef T1 pointer; //指针类型 为Del::pointer,否则为Type *
//构造函数
unique_ptr (); //11 空指针
unique_ptr (nullptr_t _Nptr); //22 空指针
explicit unique_ptr (pointer _Ptr); //33
unique_ptr (pointer _Ptr, typename conditional<is_reference<Del>::value, Del,typename add_reference<const Del>::type>::type _Deleter); //44
unique_ptr (pointer _Ptr,typename remove_reference<Del>::type&& _Deleter); //55
unique_ptr (unique_ptr&& _Right); //66
template<class Type2, Class Del2> unique_ptr (unique_ptr<Type2, Del2>&& _Right); //77
//析构函数
~unique_ptr ();
//
unique_ptr& operator= (unique_ptr&& _Right); //右值赋值
template<class Type2, Class Del2> unique_ptr& operator= (unique_ptr<Type2, Del2>&& _Right);//右值赋值
void swap (unique_ptr& _Right); //交换两个unique_ptr对象的内容
pointer release (); //在stored_ptr中存储pointer的新的值,并返回前一个值,用于放弃对所指数据对象的独占
void reset (pointer _Ptr = pointer() ); //delete当前拥有的资源并接受新的资源
pointer get () const; //返回stored_ptr
Type& operator* () const; //返回*stored_ptr
pointer operator-> () const; //返回stored_ptr
Del& get_deleter (); //返回stored_deleter的引用
const Del& get_deleter () const; //返回stored_deleter
explicit operator bool () const; //get() != pointer() 的值
unique_ptr(const unique_ptr& _Right) = delete;
unique_ptr& operator=(const unique_ptr& _Right) = delete;
private:
pointer stored_ptr; // exposition only
Del stored_deleter; // exposition only
};
unique_ptr的主要用途有:
- 實現資源取得即初始化(RAII)。當程式發生異常時,例外處理機制的棧展開(stack unwinding)會銷毀局部變數,包括unique_ptr對象,從而自動釋放資源。
- 實現右值移動語意。例如,unique_ptr類型可作為函式的返回值類型,採用了隱式地移動賦值。
- 在容器中儲存unique_ptr對象。需要顯式地移動賦值,即通過std::move()。
shared_ptr
shared_ptr是一個類別模板,包裝了使用參照計數的智慧型指標。當shared_ptr在退出其作用域、生命期結束時,解構函式或自動對所指主記憶體資料對象的參照計數減去1。如果主記憶體資料對象的參照計數為0,則會被自動delete。指向同一資源的shared_ptr共同擁有一個控制塊,其中儲存了shared_ptr的參照計數、指向這一資源的weak_ptr的數目、資源的解構器(deleter )位址、對該控制塊的可客製化的allocator。
template<class Ty>
class shared_ptr {
public:
typedef Ty element_type; //指针基类型
//构造函数
shared_ptr(); //11 空指针
shared_ptr(nullptr_t); //22 空指针
shared_ptr(const shared_ptr& sp); //33 拷贝构造
shared_ptr(shared_ptr&& sp); //44 右值移动构造
template<class Other> explicit shared_ptr(Other * ptr); //55 由其他指针初始化,构造失败时自动调用delete ptr
template<class Other, class D> shared_ptr(Other * ptr, D dtor); //66 由其他指针初始化,dtor是个可调用的函数对象,构造失败时自动调用dtor(ptr)
template<class D> shared_ptr(nullptr_t, D dtor); //77 空指针,并指定deleter
template<class Other, class D, class A> shared_ptr(Other *ptr, D dtor, A alloc);//88 alloc管理shared_ptr控制块的内存分配与释放,构造失败时自动调用dtor(ptr)
template<class D, class A> shared_ptr(nullptr_t, D dtor, A alloc);//99 空指针
template<class Other> shared_ptr(const shared_ptr<Other>& sp); //0A 拷贝构造+类型转换
template<class Other> shared_ptr(const shared_ptr<Other>&& sp); //0B 移动构造+类型转换
template<class Other> explicit shared_ptr(const weak_ptr<Other>& wp); //0C 由弱指针拷贝构造
template<class Other> shared_ptr(auto_ptr<Other>& ap); //0D 由auto_ptr指针拷贝构造
template<class Other, class D> shared_ptr(unique_ptr<Other, D>&& up); //0E 由unique_ptr移动构造
template<class Other> shared_ptr(const shared_ptr<Other>& sp, Ty *ptr); //0F
template<class Other, class D>shared_ptr(const unique_ptr<Other, D>& up) = delete;//10 禁用const unique_ptr作为移动构造函数
~shared_ptr(); // 析构函数
shared_ptr& operator=(const shared_ptr& sp); //赋值运算符
template<class Other> shared_ptr& operator=(const shared_ptr<Other>& sp); //类型转换的赋值运算符
shared_ptr& operator=(shared_ptr&& sp); //右值引用的移动语义
template<class Other> shared_ptr& operator=(shared_ptr<Other>&& sp); //右值引用+类型转换
template<class Other> shared_ptr& operator=(auto_ptr< Other >&& ap); //右值引用+auto_ptr类型转换
template <class Other, class D> shared_ptr& operator=(const unique_ptr< Other, D>& up) = delete; //禁止const型unique_ptr移动赋值给shared_ptr
template <class Other, class D> shared_ptr& operator=(unique_ptr<Other, D>&& up);//右值引用的移动语义+类型转换
void swap(shared_ptr& sp); //交换两个指针的内容
//reset成员函数,把当前指向的数据对象的引用计数减1,然后指向参数表中新的数据对象
void reset();
template<class Other> void reset(Other *ptr);
template<class Other, class D> void reset(Other *ptr, D dtor);
template<class Other, class D, class A> void reset(Other *ptr, D dtor, A alloc);
Ty *get() const; //返回所指向的数据对象的地址
Ty& operator*() const; //返回所指向的数据对象
Ty *operator->() const; //返回所指向的数据对象的地址
long use_count() const; //返回引用计数值
bool unique() const; //判断是否独占引用数据对象
operator bool() const; //判断是否指向了一个数据对象
template<class Other> bool owner_before(shared_ptr<Other> const& ptr) const;
template<class Other> bool owner_before(weak_ptr<Other> const& ptr) const;
template<class D, class Ty> D* get_deleter(shared_ptr<Ty> const& ptr);
};
weak_ptr
弱指標weak_ptr用於指向、但不擁有(即不參加參照計數)由shared_ptr管理的資源。弱指標並不直接提供對資源的訪問。應該通過shared_ptr來訪問資源。當資源的參照計數為0(所有擁有該資源的shared_ptr對象都已被銷毀),該資源將被delete,這時如果仍然有弱指標指向該資源,則該弱指標將變為expired狀態。這實際上避免了資源的迴圈參照計數。對於迴圈的參照計數,如果把其中的一個shared_ptr的指標指向改為weak_ptr的指標指向,即打破了迴圈參照。
template<class Ty> class weak_ptr {
public:
typedef Ty element_type; //指针基类型
//构造函数
weak_ptr(); //空指针
weak_ptr(const weak_ptr&); //拷贝构造
template<class Other> weak_ptr(const weak_ptr<Other>&); //类型转换的拷贝构造
template<class Other> weak_ptr(const shared_ptr<Other>&); //从shared_ptr拷贝构造
//赋值运算符
weak_ptr& operator=(const weak_ptr&);
template<class Other> weak_ptr& operator=(const weak_ptr<Other>&);
template<class Other> weak_ptr& operator=(shared_ptr<Other>&);
void swap(weak_ptr&); //交换对象的内容
void reset(); //放弃指向资源,当前对象变为空的弱指针
long use_count() const; //返回资源的引用计数
bool expired() const; //判断指向的资源是否还存在(或已经被释放)
shared_ptr<Ty> lock() const; // 获取对资源的专属拥有
};
auto_ptr
auto_ptr,即自動指標,在C++11標準是過時的類別模板。
其他Helper類
default_delete
作為使用operator new
分配主記憶體的unique_ptr對象的deleter。有兩種形式:
- 為指標: template< class T > struct default_delete; 函式呼叫成員運算子呼叫delete
- 為指標陣列: template< class T > struct default_delete<T[]>; 函式呼叫成員運算子模板呼叫delete[]
範例:
#include <memory>
#include <vector>
#include <algorithm>
int main()
{
// {
// std::shared_ptr<int> shared_bad(new int[10]);
// } // the destructor calls delete, undefined behavior
{
std::shared_ptr<int> shared_good(new int[10], std::default_delete<int[]>
());
} // the destructor calls delete[], ok
{
std::unique_ptr<int> ptr(new int(5));
} // unique_ptr<int> uses default_delete<int>
{
std::unique_ptr<int[]> ptr(new int[10]);
} // unique_ptr<int[]> uses default_delete<int[]>
// default_delete can be used anywhere a delete functor is needed
std::vector<int*> v;
for(int n = 0; n < 100; ++n)
v.push_back(new int(n));
std::for_each(v.begin(), v.end(), std::default_delete<int>());
}
allocator
allocator是STL中非常常用的類別模板,用於客製化主記憶體的分配、釋放、管理。目的是封裝STL容器在主記憶體管理上的低層細節,所以使用者程式不應該直接呼叫allocator去管理主記憶體,除非是正在客製化容器。[1]allocator將主記憶體的分配與對象的構造初始化解耦,分別用allocate、construct兩個成員函式完成;同樣將主記憶體的釋放與對象的解構銷毀解耦,分別用deallocat、destroy兩個成員函式完成。自訂的allocator的實現,必須滿足C++11標準的17.6.3.5節中的「Table 28 —— Allocator requirements」,簡單說就是要在類中定義若干類型名、模板成員名、成員函式、運算子函式。<memory>
中定義的allocator類別模板做一些使用全域malloc/free函式表達式的主記憶體的分配、釋放的平常操作:
template<class _Ty> class allocator : public _Allocator_base<_Ty>
{
public:
/*类型定义。容器类常常直接从它的allocator提取这些类型—— */
typedef _Allocator_base<_Ty> _Mybase; //基类型
typedef typename _Mybase::value_type value_type;//值类型
typedef value_type _FARQ *pointer; //指针类型
typedef value_type _FARQ& reference; //引用类型
typedef const value_type _FARQ *const_pointer; //常量指针类型
typedef const value_type _FARQ& const_reference;//常量引用类型
typedef size_t size_type; //size类型
typedef ptrdiff_t difference_type; //地址差值类型
/*类型操作函数: */
template<class _Other>
struct rebind //用于'''重绑定'''的成员模板: convert this type to allocator<_Other>。
//因为容器类需要动态申请内存的往往不是value_type,而是诸如List Node这样的其他类型
{
typedef allocator<_Other> other;
};
pointer address(reference _Val) const
{ // 把mutuable引用变换为地址返回
return ((pointer) &(char&)_Val);
}
const_pointer address(const_reference _Val) const
{ //把只读引用变换为只读地址返回
return ((const_pointer) &(char&)_Val);
}
/*构造函数:*/
allocator() throw( )
{ //缺省构造,不抛出异常
}
allocator(const allocator<_Ty>&) throw()
{ // 空的拷贝构造
}
template<class _Other>
allocator(const allocator<_Other>&) throw()
{ //从相关的allocator做拷贝构造,内容为空
}
template<class _Other>
allocator<_Ty>& operator=(const allocator<_Other>&)
{ //从相关的allocator做拷贝赋值运算符,内容为空
return (*this);
}
/*以下为业务函数: */
void deallocate(pointer _Ptr, size_type) //释放内存,并不析构对象。第二个参数为元素的项数
{ // deallocate object at _Ptr, ignore size
::operator delete(_Ptr);
}
pointer allocate(size_type _Count) //获取内存资源,并不调用对象的构造函数
{ // allocate array of _Count elements
return (_Allocate(_Count, (pointer)0));
}
pointer allocate(size_type _Count, const void _FARQ *)
{ // allocate array of _Count elements, ignore hint 为高性能而设计的allocator可能会利用第二个参数的提示
return (allocate(_Count));
}
void construct(pointer _Ptr, const _Ty& _Val) // construct object at _Ptr with value _Val
{ //实际上调用了“带位置的new运算符表达式”,实现在特定位置上调用构造函数。
_Construct(_Ptr, _Val);
}
void construct(pointer _Ptr, _Ty&& _Val) //construct object at _Ptr with right-value _Val
{ 实际上调用了“带位置的new运算符表达式”,以及右值完美转发,实现在特定位置上调用构造函数。
::new ((void _FARQ *)_Ptr) _Ty(_STD forward<_Ty>(_Val));
}
template<class _Other>
void construct(pointer _Ptr, _Other&& _Val) // construct object at _Ptr with value _Val, 带类型转换
{
::new ((void _FARQ *)_Ptr) _Ty(_STD forward<_Other>(_Val));
}
void destroy(pointer _Ptr)
{ // 摧毁在地址 _Ptr的对象,但不释放内存
_Destroy(_Ptr);
}
_SIZT max_size() const _THROW0()
{ // 估计allocator能分配的数据对象的最大可能数目
_SIZT _Count = (_SIZT)(-1) / sizeof (_Ty);
return (0 < _Count ? _Count : 1);
}
};
allocator_traits
allocator_traits是對allocator的包裝。
bad_weak_ptr
bad_weak_ptr是個異常類。在用弱指標拷貝構造共享指標時,如果弱指標是expired,那麼拷貝建構函式丟擲bad_weak_ptr異常。
enable_shared_from_this
enable_shared_from_this是一個helper類別模板,用於作為資料類的基礎類別。當資料類的對象已經被shared_ptr所擁有,如果需要從該資料類對象獲得其shared_ptr指標,就需要呼叫作為基礎類別的enable_shared_from_this的成員函式shared_from_this
。例如:
struct Data : public enable_shared_from_this<Data> { };
int main() {
shared_ptr<Data> a (new Data);
Data& r = *a;
shared_ptr<Data> b = r.shared_from_this();
}
在上例中,b與a共同擁有對資料類對象的同一套參照計數,因此是正確的。如果如此構造b:
shared_ptr b( &r);
則對同一個資料類對象,弄出了兩套參照計數。這會導致當一套參照計數變為0而銷毀資料類對象時,另外一套參照計數的shared_ptr成為「懸空」的錯誤。
Microsoft Visual C++ 2010是在enable_shared_from_this類別模板中定義一個資料類型_EStype
。shared_ptr的對象在呼叫建構函式時,最後將呼叫一個函式_Enable_shared(_Ty *_Ptr, _Ref_count_base *_Refptr),其中第一個參數是資料類對象的指標,第二個參數是參照計數控制塊的指標。利用模板元程式設計的參數推導的特性,編譯器選擇_Enable_shared,或是匹配下述模板函式
template<class _Ty> inline void _Enable_shared(_Ty *_Ptr, _Ref_count_base *_Refptr, typename _Ty::_EStype * = 0)
或是匹配下述普通函式
inline void _Enable_shared(const volatile void *, const volatile void *) { }
前者把enable_shared_from_this類中把一個弱指標指向了shared_ptr的參照計數控制塊。從而實現了enable_shared_from_this類的語意。
pointer_traits
pointer_traits是一個類別模板,描述指標類型所需要的一些類型的定義。
- 成員類型
- pointer 指標類型
- element_type 指標的基本類型
- difference_type 一般是std::ptrdiff_t
- rebind 成員類別模板,指向需要繫結的其它類型
- 靜態成員函式
- pointer_to 從參照類型返回其相應的指標類型,一般為std::addressof(r)
raw_storage_iterator
raw_storage_iterator是個類別模板,用於表示指向主記憶體的一個前向迭代器。其設定運算子在當前所指向主記憶體上呼叫值類型的建構函式,生成一個對象。
函式
- void* align( std::size_t alignment,std::size_t size,void*& ptr,std::size_t& space );在一塊主記憶體(ptr所指)中,按照對齊要求slignment,找到一塊長度為size的,並重設ptr為該主記憶體起始位址,space為該主記憶體長度。
- allocate_shared 這是shared_ptr的工廠函式,指定主記憶體分配器allocator與資料對象的初始化參數,建立資料對象及shared_ptr。
- const_pointer_cast 用於shared_ptr對象的const的類型轉換。
- declare_no_pointers 用於垃圾回收。告知垃圾收集器在指定的主記憶體範圍內不含可跟蹤的指標。
- declare_reachable 用於垃圾回收。令垃圾收集器知道參數指標是指向一塊聲明為可達的動態分配主記憶體。聲明為可達的(declared reachable)完全對象,[2]是指該對象作為形參所指,declare_reachable呼叫的次數超過undeclare_reachable呼叫次數。聲明為可達的一塊動態分配主記憶體不能被垃圾收集器釋放,即使它看起來沒有可達的訪問方法。
- dynamic_pointer_cast 對shared_ptr對象的執行時動態轉換類型。
- get_deleter 返回shared_ptr對象的deleter。
- get_pointer_safety 用於垃圾回收。返回當前的垃圾回收器採用的指標類型。
- get_temporary_buffer 對給定類型、給定對象數目,分配一塊臨時的主記憶體儲存。
- make_shared 這是shared_ptr的工廠函式,使用給定的資料對象的初始化參數,建立資料對象及shared_ptr。
- owner_less 混合比較shared_ptr對象與weak_ptr對象的序關係。
- pointer_safety 用於垃圾回收。是
get_pointer_safety
的返回值的列舉類型。 - return_temporary_buffer 釋放由
get_temporary_buffer
分配的臨時性的主記憶體塊。 - static_pointer_cast 編譯時靜態轉換shared_ptr的值類型。
- swap 交換兩個shared_ptr對象或交換兩個weak_ptr對象.
- undeclare_no_pointers 用於垃圾回收。告訴垃圾回收器,在指定範圍的主記憶體中,存著可跟蹤指標。
- undeclare_reachable 用於垃圾回收。告訴垃圾回收器,被形參指標所指的動態分配主記憶體塊,復原一次declare_reachable操作。如果動態分配主記憶體塊上沒有declare_reachable操作,則垃圾收集器如果判斷它已經是不可達的狀態(即沒有手段訪問這塊主記憶體)就可把它當垃圾回收。
- uninitialized_copy 把輸入iterator所給出的一個主記憶體範圍內的對象陣列的每個元素,逐個拷貝構造到前向的目標iterator所指的主記憶體上。
- uninitialized_copy_n 把輸入iterator所給出的一個主記憶體位址開始的對象陣列的n個元素,逐個拷貝構造到前向的目標iterator所指的主記憶體上。
- uninitialized_fill 在輸入iterator所給出的一個主記憶體範圍內,用給定的對象去拷貝構造多個對象。
- uninitialized_fill_n 在輸入iterator所給出的一個主記憶體開始位址處,用給定的對象去拷貝構造指定的n個對象。
例子
共享指標
#include <memory>
#include <iostream>
class Test
{
public:
Test()
{
std::cout << "Test()" << std::endl;
}
~Test()
{
std::cout << "~Test()" << std::endl;
}
};
int main()
{
std::shared_ptr<Test> p1 = std::make_shared<Test>();
std::cout << "1 ref:" << p1.use_count() << std::endl;
{
std::shared_ptr<Test> p2 = p1;
std::cout << "2 ref:" << p1.use_count() << std::endl;
}
std::cout << "3 ref:" << p1.use_count() << std::endl;
return 0;
}
弱指標例子
#include <iostream>
#include <memory>
class TestB;
class TestA
{
public:
TestA()
{
std::cout << "TestA()" << std::endl;
}
void ReferTestB(std::shared_ptr<TestB> test_ptr)
{
m_TestB_Ptr = test_ptr;
}
void TestWork()
{
std::cout << "~TestA::TestWork()" << std::endl;
}
~TestA()
{
std::cout << "~TestA()" << std::endl;
}
private:
std::weak_ptr<TestB> m_TestB_Ptr;
};
class TestB
{
public:
TestB()
{
std::cout << "TestB()" << std::endl;
}
void ReferTestB(std::shared_ptr<TestA> test_ptr)
{
m_TestA_Ptr = test_ptr;
}
void TestWork()
{
std::cout << "~TestB::TestWork()" << std::endl;
}
~TestB()
{
////把std::weak_ptr类型转换成std::shared_ptr类型
std::shared_ptr<TestA> tmp = m_TestA_Ptr.lock();
tmp->TestWork();
std::cout << "2 ref a:" << tmp.use_count() << std::endl;
std::cout << "~TestB()" << std::endl;
}
std::weak_ptr<TestA> m_TestA_Ptr;
};
int main()
{
std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();
std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();
ptr_a->ReferTestB(ptr_b);
ptr_b->ReferTestB(ptr_a);
std::cout << "1 ref a:" << ptr_a.use_count() << std::endl;
std::cout << "1 ref b:" << ptr_a.use_count() << std::endl;
return 0;
}
獨占指標的例子
#include <iostream>
int main()
{
std::unique_ptr<int> pInt;
pInt.reset(new int());
int *p = pInt.release(); //释放所有权
//由于unique_ptr有std::unique_ptr<T[]>的重载函数,所以它可以用来管理数组资源
std::unique_ptr<int[]> pArray(new int[3]{1,3,3});
}
參考文獻
- ^ The Standard Librarian: What Are Allocators Good For? 作者:Matt Austern. [2013-08-10]. (原始內容存檔於2020-07-31).
- ^ C++11標準20.6.4-1
- 《Boost程式庫完全開發指南——深入C++「准」標準庫》第3章 主記憶體管理 3.4 shared_ptr 作者: 羅劍鋒 電子工業出版社 2010年9月版 ISBN 9787121115776