这篇文章主要来源于:codeguru网站的一篇文章:A TR1 tutorial:smart pointer (详细介绍了C++的智能指针,尤其是shared_ptr)。
众所周知,在 TR1 之前,C++标准库中的智能指针只有auto_ptr,但由于它的【排他所有权模式】(exclusive ownership model)导致了许多问题,为解决,C++TR1中引入了 boost 开源库中的智能指针:shared_ptr 和 weak_ptr 并使之成为了标准库的一部分(C++11标准)。
注1:C++ TR1 即 C++ Technical Report 1 是 ISO/IEC TR 19768, C++ Library Extensions(函数库扩充)的一般名称,它是针对 C++ 标准库的第一次扩展。
注2:C++最新标准:C++11已将智能指针:shared_ptr、weak_ptr收录为标准库中,即对应为:std::shared_ptr, std::weak_ptr,相应的头文件:<memory>(相比TR1:头文件:<tr1/memory>).
注3:若读者编译器不支持C++11标准,则编译时:1.将头文件由<memory> 改为 <tr1/memory>; 2.将namespace由 std:: 改为 std::tr1 .
一、智能指针类:std::auto_ptr
由于 auto_ptr 基于【排他所有权模式】,这意味着:两个指针(同类型)不能指向同一个资源,复制或赋值都会改变资源的所有权。
一个简单的例子1:
- #include <iostream>
- #include <memory>
- class A
- {
- public:
- void print(){std::cout<<"A::print"<<std::endl;}
- };
- int main()
- {
- std::auto_ptr<A>pa1(new A);
- pa1->print();
- std::cout<<"pa1 pointer:"<<pa1.get()<<std::endl;
-
- std::auto_ptr<A>pa2(pa1);
- pa2->print();
- std::cout<<"pa1 pointer:"<<pa1.get()<<std::endl;
- std::cout<<"pa2 pointer:"<<pa2.get()<<std::endl;
-
- return 0;
- }
输出
:
即即经过复制构造之后,pa1所指向资源的所有权转向了pa2,而pa1变成空,二者不能同时共享该资源。
auto_ptr 主要有两大问题:
- 复制和赋值会改变资源的所有权,不符合人的直觉。
- 在 STL 容器中无法使用auto_ptr ,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
二、C++11中新增的智能指针
新加入标准模板库(STL)的智能指针有两个:
shared_ptr:基于引用计数模型。每次有 shared_ptr 对象指向资源,引用计数器就加1;当有 shared_ptr 对象析构时,计数器减1;当计数器值为0时,被指向的资源将会被释放掉。且该类型的指针可复制和可赋值,即其可用于STL容器中。此外,shared_ptr 指针可与多态类型和不完全类型一起使用。主要缺点:无法检测出循环引用(后面会细说),如一颗树,其中既有指向孩子结点的指针又有指向父亲结点的指针,即孩子父亲相互引用。这会造成资源无法释放,从而导致内存泄露。为了 fix 这个问题,引入了另一个智能指针:weak_ptr.
weak_ptr:指向有shared_ptr 指向的资源(即其需要shared_ptr的参与,其辅助 shared_ptr 之用),但是不会导致计数。一旦计数器为0,不管此时指向资源的 weak_ptr 指针有多少,资源都会被释放,而所有的这些 weak_ptr 指针会被标记为无效状态(即 weak_ptr作为观察shared_ptr 的角色存在着,shared_ptr 不会感受到 weak_ptr 的存在)。
上一例子的shared_ptr 实现-例子2:
- #include <iostream>
- #include <memory>
- class A
- {
- public:
- void print(){std::cout<<"A::print"<<std::endl;}
- };
- int main()
- {
- std::shared_ptr<A>sp1(new A);
- sp1->print();
- std::cout<<"sp1 pointer:"<<sp1.get()<<std::endl;
-
- std::shared_ptr<A>sp2(sp1);
- sp2->print();
- std::cout<<"sp1 pointer:"<<sp1.get()<<std::endl;
- std::cout<<"sp2 pointer:"<<sp2.get()<<std::endl;
-
- std::cout<<"count sp1:"<<sp1.use_count()<<std::endl;
- std::cout<<"count sp2:"<<sp2.use_count()<<std::endl;
-
- return 0;
- }
输出
:
可知:sp2创建后,sp1对资源的所有权并没有被剥夺,而是sp1 和 sp2 均指向了资源,且此时资源的引用计数为2。当两个shard_ptr 指针sp1、sp2 超过其作用域时,最后一个析构的指针将会致使资源的释放(因为引用计数为0了)。
三、智能指针类:std::tr1::shared_ptr
3.1 概念
std::shared_ptr 智能指针共享所指向的资源(所有权),即几个 shared_ptr 可同时拥有一个对象,且共享一个控制块(constrol block),包含指向资源的 shared_ptr对象个数、指向资源的 weak_ptr 对象个数以及删除器(deleter:用户自定义的用于释放资源的函数,可以默认没有)。
一个空的 shared_ptr 对象不拥有任何资源和控制块。另一方面,一个 shared_ptr 初始化为一个NULL 指针和一个控制块,这有别有空的 shared_ptr。当共享的引用计数器为0时,资源释放(delete 操作符释放,或由用户提供的 删除器 释放它)。
3.2 使用
(1)创建一个 shared_ptr 对象
常见,一个 shared_ptr 对象可由以下四种对象来构造:
- 指向任何类型 T 的指针(包括 const T),也可为指向的资源指定删除器释放它;
- 另一个 shared_ptr 对象;
- 一个 weak_ptr 对象;
- 一个 auto_ptr 对象。
它们对应的构造函数如下:
-
- template<class T>
- explicit shared_ptr(T*);
- template<class T, class D>
- shared_ptr(T*, D);
-
- template<class T>
- shared_ptr(const shared_ptr<T>&);
-
- template<class T>
- shared_ptr(const weak_ptr<T>&);
-
- shared_ptr(const auto_ptr&);
(2)删除器(deleter)与 get_deleter函数:
get_deleter函数返回一个指针,指向shared_ptr 的删除器,如果没有提供删除器则返回0。
例子3:
- #include <iostream>
- #include <memory>
- class A
- {
- public:
- static A* alloc()
- {
- A* pa = new A;
- std::cout<<"a new object was created"<<std::endl;
- return pa;
- }
- static void free(A* pa)
- {
- delete pa;
- std::cout<<"A object was destroyed"<<std::endl;
- }
- };
- typedef void(*deleter)(A*);
- int main()
- {
- std::shared_ptr<A> spa(A::alloc(), &A::free);
-
- deleter* del = std::get_deleter<deleter>(spa);
- std::cout<<"get_deleter(spa)!=0 == "<<std::boolalpha<<(del!=0)<<std::endl;
- return 0;
- }
输出
:
(3)-> 、 * 操作符和 get 函数
shared_ptr 类重载了-> 操作符和 * 操作符,前者返回指向资源的指针;后者指向资源的引用。故无需内部指针。其原型如下:
- template<class T>
- class shared_ptr
- {
- public:
- T* get() const;
- T& operator*()const;
- T* operator->()const;
- };
其中 get 函数返回指向资源的指针,基本等同于 ->操作符,且与auto_ptr 兼容。
例子4:
- #include <iostream>
- #include <memory>
- class A
- {
- public:
- void print(){std::cout<<"A::print"<<std::endl;}
- };
- int main()
- {
- std::shared_ptr<A> sp(new A);
- A* pa = sp.get();
- if(pa)pa->print();
- std::cout<<"-> operator: ";
- sp->print();
- std::cout<<"* operator: ";
- (*sp).print();
-
- return 0;
- }
输出
:
(4)条件操作符(bool operator)
shared_ptr 类提供了布尔操作符,允许 shared_ptr 对象用于布尔表达式去检查是否该shared_ptr对象里的指针为NULL。
例子5:
- #include <iostream>
- #include <memory>
- #include <string>
- int main()
- {
- std::shared_ptr<std::string> sp1;
- if(sp1)
- {
- std::cout<<"pointer in sp1 is not NULL"<<std::endl;
- }
- else
- {
- std::cout<<"pointer in sp1 is NULL"<<std::endl;
- }
-
- std::shared_ptr<std::string> sp2(new std::string("hello world"));
- if(sp2)
- {
- std::cout<<"pointer in sp2 is not NULL"<<std::endl;
- }
- else
- {
- std::cout<<"pointer in sp2 is NULL"<<std::endl;
- }
-
- return 0;
- }
输出
:
(5)交换与赋值(Swap and assignment)
I、函数原型:void swap(shared_ptr& r); //交换*this 与 r 的内容
II、赋值操作符:operator= ,重载后可将shared_ptr 或 auto_ptr 对象赋值给 shared_ptr 对象。
原型如下:
- template<class T>
- shared_ptr& operatork=(const shared_ptr<T>&r);
- template<class T>
- shared_ptr& operator=(const std::auto_ptr<T>& r);
- };
例子6
:
- #include <iostream>
- #include <memory>
- #include <string>
- void isEmpty(std::shared_ptr<std::string>& r)
- {
- if(r)
- {
- std::cout<<"pointer in shared_ptr is not NULL"<<std::endl;
- }
- else
- {
- std::cout<<"pointer in shared_ptr is NULL"<<std::endl;
- }
-
- }
- int main()
- {
- std::cout<<"before swap:"<<std::endl;
- std::shared_ptr<std::string> sp1;
- std::cout<<"sp1: ";
- isEmpty(sp1);
- std::shared_ptr<std::string> sp2(new std::string("hello world"));
- std::cout<<"sp2: ";
- isEmpty(sp2);
-
- sp1.swap(sp2);
- std::cout<<"after swap:"<<std::endl;
- std::cout<<"sp1: ";
- isEmpty(sp1);
- std::cout<<"sp2: ";
- isEmpty(sp2);
- std::cout<<"before operator=:"<<std::endl;
- std::cout<<"sp1: "<<sp1<<", *sp1: "<<*sp1<<std::endl;
- std::cout<<"sp2: ";
- isEmpty(sp2);
- sp2 = sp1;
- std::cout<<"after operator=:"<<std::endl;
- std::cout<<"sp1: "<<sp1<<", *sp1: "<<*sp1<<std::endl;
- std::cout<<"sp2: "<<sp2<<", *sp2: "<<*sp2<<std::endl;
-
- return 0;
- }
输出
:
(6)unique 与 use_count函数
函数原型:
-
- long use_count() const;
-
-
- bool unique() const;
例子7
:
- #include <iostream>
- #include <memory>
- #include <string>
- int main()
- {
- std::shared_ptr<std::string> sp1(new std::string("hello world"));
- std::cout<<"unique:"<<std::boolalpha<<sp1.unique()<<std::endl;
- std::cout<<"count:"<<sp1.use_count()<<std::endl;
-
- std::shared_ptr<std::string> sp2(sp1);
- std::cout<<"unique:"<<std::boolalpha<<sp1.unique()<<std::endl;
- std::cout<<"sp1 count:"<<sp1.use_count()<<std::endl;
- std::cout<<"sp2 count:"<<sp2.use_count()<<std::endl;
-
- return 0;
- }
输出
:
(7)reset函数
函数原型:
-
- void reset();
-
- template<class Y>
- void reset(Y* ptr);
-
- template<class Y, class Deleter>
- void reset(Y* ptr, Deleter d);
该函数用 ptr 指针指向的资源替换掉当前shared_ptr 管理的资源,使 shared_ptr对象管理新的资源(pointered by ptr),以前资源对应的share_ptr 对象的引用计数减1。如果reset函数的参数为空,则表示*this(当前share_ptr 对象)退出共享资源。
例子8:
- #include <iostream>
- #include <memory>
- class A
- {
- private:
- int m_x;
- public:
- explicit A(int x =0):m_x(x){}
- int getX(){return m_x;}
- int setX(int x){m_x = x;}
- void print(){std::cout<<"A::print"<<std::endl;}
- static A* alloc(int x)
- {
- A* pa = new A(x);
- std::cout<<"a new object was created"<<std::endl;
- }
- static void free(A* pa)
- {
- std::cout<<"x: "<<pa->getX();
- delete pa;
- std::cout<<",A object was destroyed"<<std::endl;
- }
- };
- int main()
- {
- std::shared_ptr<A> sp1(new A(10),&A::free);
- std::shared_ptr<A> sp2(sp1);
- std::shared_ptr<A> sp3(sp1);
- std::cout<<"sp1 x :"<<sp1->getX()<<std::endl;
- std::cout<<"sp2 x :"<<sp2->getX()<<std::endl;
- std::cout<<"sp3 x :"<<sp3->getX()<<std::endl;
- std::cout<<"sp1 count:"<<sp1.use_count()<<std::endl;
- std::cout<<"sp2 count:"<<sp2.use_count()<<std::endl;
- std::cout<<"sp3 count:"<<sp3.use_count()<<std::endl;
-
- sp1.reset();
- A* pa = new A(20);
- sp2.reset(pa,&A::free);
- std::cout<<"after reset:"<<std::endl;
- if(NULL == sp1)
- {
- std::cout<<"pointer in sp1 is NULL"<<std::endl;
- }
-
- std::cout<<"sp2 x :"<<sp2->getX()<<std::endl;
- std::cout<<"sp3 x :"<<sp3->getX()<<std::endl;
- std::cout<<"sp1 count:"<<sp1.use_count()<<std::endl;
- std::cout<<"sp2 count:"<<sp2.use_count()<<std::endl;
- std::cout<<"sp3 count:"<<sp3.use_count()<<std::endl;
-
- return 0;
- }
输出
:
(8)shared_ptr 在 STL 容器中的应用
由前面提到,shared_ptr 相比于 auto_ptr,可复制和赋值,故可用于 STL 容器中。
下面的例子9:将shared_ptr<int> 放入容器中,并对每个容器中的元素进行操作,如使shared_ptr 指向的int 变量 变为原来的2倍。
- #include <iostream>
- #include <memory>
- #include <vector>
- #include <algorithm>
- std::shared_ptr<int> double_it(const std::shared_ptr<int>& sp)
- {
- *sp *= 2;
- return sp;
- }
- int main()
- {
- std::vector<std::shared_ptr<int>> numbers;
-
- numbers.push_back(std::shared_ptr<int>(new int(1)));
- numbers.push_back(std::shared_ptr<int>(new int(2)));
- numbers.push_back(std::shared_ptr<int>(new int(3)));
-
- std::cout<<"initially"<<std::endl;
- for(std::vector<std::shared_ptr<int>>::const_iterator it = numbers.begin(); it != numbers.end(); it++)
- {
- std::cout<<*(*it)<<"(count = "<<(*it).use_count()<<")"<<std::endl;
- }
-
- std::transform(numbers.begin(), numbers.end(), numbers.begin(), double_it);
-
- std::cout<<"after transformation"<<std::endl;
- for(std::vector<std::shared_ptr<int>>::const_iterator it = numbers.begin(); it != numbers.end(); it++)
- {
- std::cout<<*(*it)<<"(count = "<<(*it).use_count()<<")"<<std::endl;
- }
-
- return 0;
- }
输出
:
(9)shared_ptr 在类层次结构中的应用
如 D 是 B 的子类,则可用shared_ptr<B>类型的对象(基类指针)接收shared_ptr<D>类型的对象(派生类指针)。
例子10:
- #include <iostream>
- #include <memory>
- #include <string>
- #include <vector>
-
- class Item
- {
- private:
- std::string title_;
- public:
- explicit Item(const std::string& title):title_(title){}
- virtual ~Item(){}
-
- virtual std::string Description() const = 0;
- std::string getTitle()const {return title_;}
- };
- class Book: public Item
- {
- private:
- int pages_;
- public:
- Book(const std::string& title, int pages):Item(title),pages_(pages){}
-
- virtual std::string Description()const {return "Book: " + getTitle();}
- int getPages()const {return pages_;}
- };
-
- class DVD: public Item
- {
- private:
- int tracks_;
- public:
- DVD(const std::string& title, int tracks):Item(title),tracks_(tracks){}
-
- virtual std::string Description() const {return "DVD: " + getTitle();}
- int getTracks()const {return tracks_;}
- };
- int main()
- {
- std::vector<std::shared_ptr<Item>> items;
- items.push_back(std::shared_ptr<Book>(new Book("C++ Primer",745)));
- items.push_back(std::shared_ptr<DVD>(new DVD("MrVanGogh",9)));
-
- for(std::vector<std::shared_ptr<Item>>::const_iterator it = items.begin(); it != items.end(); it++)
- {
- std::cout<<(*it)->Description()<<std::endl;
- }
-
- return 0;
- }
输出:
(10)cast 操作符
C++ 中提供了四种强制类型转换操作符:static_cast, dynamic_cast, const_cast, reinterpret_cast。而关于shared_ptr 无法利用这些原始的操作符进行转换,其定义了自己的类型转换操作符:static_pointer_cast, dynamic_pointer_cast, const_pointer_cast 。
如【9】中提到的 “若 D 是 B的子类 ”,其为向上转换,但能否向下转换呢?即从 shared_ptr<B> 到 shared_ptr<D> (当然,前提是:shared_ptr<B> 已指向了一个D对象,现在要做的就是“还原它”)。
I、使用 std::dynamic_pointer_cast,可以达到目的:
- template<class D, class B>
- shared_ptr<D> dynamic_pointer_cast(const shared_ptr<B>& r);
该函数不会抛出任何异常(noexcept)。若执行成功(
前提:
shared_ptr<B>对象 r 已经指向了一个D对象),则返回 shared_ptr<D> 共享资源的所有权,否则返回一个空对象。
例子11:
- #include <iostream>
- #include <memory>
- #include <string>
- class Item
- {
- private:
- std::string title_;
- public:
- explicit Item(const std::string& title):title_(title){}
- virtual ~Item(){}
-
- virtual std::string Description() const = 0;
- std::string getTitle()const {return title_;}
- };
- class Book: public Item
- {
- private:
- int pages_;
- public:
- Book(const std::string& title, int pages):Item(title),pages_(pages){}
-
- virtual std::string Description()const {return "Book: " + getTitle();}
- int getPages()const {return pages_;}
- };
-
- class DVD: public Item
- {
- private:
- int tracks_;
- public:
- DVD(const std::string& title, int tracks):Item(title),tracks_(tracks){}
-
- virtual std::string Description() const {return "DVD: " + getTitle();}
- int getTracks()const {return tracks_;}
- };
- int main()
- {
- std::shared_ptr<Item> spi(new DVD("MrVanGogh", 9));
- std::cout<<"spi counter: "<<spi.use_count()<<std::endl;
-
- std::shared_ptr<Book> spb = std::dynamic_pointer_cast<Book>(spi);
- if(spb)
- {
- std::cout<<spb->getTitle()<<", "<<spb->getPages()<<std::endl;
- }
- else
- {
- std::cout<<"pointer in spb is NULL"<<std::endl;
- }
-
- std::shared_ptr<DVD> spd = std::dynamic_pointer_cast<DVD>(spi);
- if(spd)
- {
- std::cout<<spd->getTitle()<<", "<<spd->getTracks()<<std::endl;
- }
- else
- {
- std::cout<<"pointer in spd is NULL"<<std::endl;
- }
-
- std::cout<<"spi counter: "<<spi.use_count()<<std::endl;
- std::cout<<"spb counter: "<<spb.use_count()<<std::endl;
- std::cout<<"spd counter: "<<spd.use_count()<<std::endl;
-
-
- return 0;
- }
输出
:
II、static_pointer_cast
根据 static_cast 的知识:编译器隐式执行的任何类型转换都可以由static_cast 显示完成(如 int -> char;); 如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的(如,找回存放在 void* 指针中的值)。
注意:static_cast 转换的一个特点就是:它只会生成原变量的副本,不会对原变量有任何修改。
而static_pointer_cast 工作的前提是:static_cast<T*>(r.get()) 必须是有效的。二者理念相同!
例子12:
- #include <iostream>
- #include <memory>
- #include <vector>
-
- int main()
- {
- std::vector<std::shared_ptr<void>> items;
-
- std::shared_ptr<char> sp1(new char('A'));
- std::shared_ptr<int> sp2(new int(66));
-
- std::cout<<"after creating the shared_ptr"<<std::endl;
- std::cout<<"sp1 counter: "<<sp1.use_count()<<std::endl;
- std::cout<<"sp2 counter: "<<sp2.use_count()<<std::endl;
-
- items.push_back(sp1);
- items.push_back(sp2);
-
- std::cout<<"after adding to the vector"<<std::endl;
- std::cout<<"sp1 counter: "<<sp1.use_count()<<std::endl;
- std::cout<<"sp2 counter: "<<sp2.use_count()<<std::endl;
-
- std::shared_ptr<char> spc1 = std::static_pointer_cast<char>(*(items.begin()));
- if(spc1)
- {
- std::cout<<"&spc1: "<<&spc1<<std::endl;
- std::cout<<"*spc1: "<<*spc1<<std::endl;
- }
-
- std::shared_ptr<char> spc2 = std::static_pointer_cast<char>(*(items.begin()+1));
- if(spc2)
- {
- std::cout<<"&spc2: "<<&spc2<<std::endl;
- std::cout<<"*spc2: "<<*spc2<<std::endl;
- }
- std::shared_ptr<short> spd2 = std::static_pointer_cast<short>(*(items.begin()+1));
- if(spd2)
- {
- std::cout<<"&spd2: "<<&spd2<<std::endl;
- std::cout<<"*spd2: "<<*spd2<<std::endl;
- }
- std::cout<<"after casting"<<std::endl;
- std::cout<<"sp1 couner: "<<sp1.use_count()<<std::endl;
- std::cout<<"spc1 couner: "<<spc1.use_count()<<std::endl;
- std::cout<<"sp2 couner: "<<sp2.use_count()<<std::endl;
- std::cout<<"spc2 couner: "<<spc2.use_count()<<std::endl;
- std::cout<<"spd2 couner: "<<spd2.use_count()<<std::endl;
-
- return 0;
- }
输出
:
III、const_pointer_cast
若 const_cast<T*>(sp.get()) 返回一个NULL指针,则 std::cosnt_pointer_cast 返回一个空的对象。否则,它返回一个shared_ptr<T>对象。
原型:
- template<classT,class U>
- shared_ptr<T>cosnt_pointer_cast(const shared_ptr<U>& r);
四、智能指针:std::tr1::weak_ptr
引入:由于 shared_ptr 智能指针的缺陷:无法检测循环引用(关于 share_ptr 的循环引用详细内容,可参见我的另一篇文章:Effective shared_ptr)---这是所有引用型智能指针的硬伤。为了解决这个问题又引入了 weak_ptr 指针。
概念:该类型智能指针也是指向由 shared_ptr 所指向的资源,但是不增加引用计数,故称为”弱引用(weak reference)“ 。当最后拥有资源的最后一个 shared_ptr 对象离开其作用域时,资源被释放,weak_ptr 被标记为无效状态。并且可以通过函数 expired() 来检查是是否 weak_ptr 处于无效状态。weak_ptr 在访问所引用的对象前必须先转换为 shared_ptr, 即其【来于 shared_ptr 也去于 shared_ptr】。
作用(观察者的角度):weak_ptr 是用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可以被他人删除时,可以使用 weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 shared_ptr ,此时若原来的 shared_ptr 被销毁,则该对象的生命周期将延长至这个临时生成的 shared_ptr 同样被销毁为止。
线程安全:考虑到多线程环境下,可通过std::weak_ptr::lock函数创建一个新的shared_ptr 管理对象的共享所有权-----作为临时访问的shared_ptr(此时引用计数是会增1 的,即此临时的 shared_ptr 若没有离开其作用域,共享的资源是不会被释放的)。如果没有管理的对象,即*this 为空,则返回的 shared_ptr 也是空;否则返回:shared_ptr<T>(*this) 。
例子13:
- #include <iostream>
- #include <memory>
-
- void show(const std::weak_ptr<int>& wp)
- {
- std::shared_ptr<int> sp = wp.lock();
- std::cout<<*sp<<std::endl;
- }
- int main()
- {
- std::weak_ptr<int> wp;
-
- {
- std::shared_ptr<int> sp(new int(10));
- wp = sp;
-
- show(wp);
- }
-
- std::cout<<"expire: "<<std::boolalpha<<wp.expired()<<std::endl;
-
- return 0;
- }
输出
: