智能指针是一个类,这个类的构造函数中传入一个普通指针,然后开辟空间,析构函数中释放传入的指针。智能指针的类都是栈上的对象,所以当函数(或程序)结束时会自动被释放,
使用注意点:
shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg; // not allowed (implicit conversion)
pd = shared_ptr<double>(p_reg); // allowed (explicit conversion)
shared_ptr<double> pshared = p_reg; // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg); // allowed (explicit conversion)
string vacation("I wandered lonely as a cloud.");
shared_ptr<string> pvac(&vacation); // No
pvac过期时,程序将把delete运算符用于非堆内存,这是错误的。
智能指针都具有RAII的思想,即构造函数获得资源,析构函数清理资源,但是当用一个智能指针拷贝构造另一个智能指针的时候,有可能会有浅拷贝的问题,这个空间会被释放多次,智能指针的发展就是围绕着指针拷贝问题而走。
C++98里面有一个智能指针auto_ptr。
因为auto_ptr有缺陷,但是C++标准里面从C++98到C++11之间没有出现新的智能指针能解决这个缺陷,所以在这段时间内,boost这个官方组织就增加了智能指针(scoped_ptr,shared_ptr,weak_ptr等)
使用:包含头文件
std::auto_ptr<server_t> server = std::auto_ptr<server_t>(new server_t());
示例:
auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation;
vocaticn = ps;
上述赋值语句将完成什么工作呢?如果ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。要避免这种问题,方法有多种:
unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权,unique_ptr还可能没有对象,这种情况被称为empty。
//智能指针的创建
unique_ptr<int> u_i; //创建空智能指针
u_i.reset(new int(3)); //"绑定”动态对象
unique_ptr<int> u_i2(new int(4));//创建时指定动态对象
unique_ptr<T,D> u(d); //创建空unique_ptr,执行类型为T的对象,用类型为D的对象d来替代默认的删除器delete
//所有权的变化
int *p_i = u_i2.release(); //释放所有权
unique_ptr<string> u_s(new string("abc"));
unique_ptr<string> u_s2 = std::move(u_s); //所有权转移(通过移动语义),u_s所有权转移后,变成“空指针”
u_s2.reset(u_s.release());//所有权转移
u_s2=nullptr;//显式销毁所指对象,同时智能指针变为空指针。与u_s2.reset()等价
unique_ptr<string> p3 (new string ("auto"); //#1
unique_ptr<string> p4; //#2
p4 = p3; //#3
编译器认为语句#3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。
但unique_ptr还有更聪明的地方。有时候,会将一个智能指针赋给另一个并不会留下危险的悬挂指针。假设有如下函数定义:
unique_ptr<string> demo(const char * s)
{
unique_ptr<string> temp (new string (s));
return temp;
}
并假设编写了如下代码:
unique_ptr<string> ps;
ps = demo('Uniquely special");
demo()返回一个临时unique_ptr,然后ps接管了原本归返回的unique_ptr所有的对象,而返回时临时的 unique_ptr 被销毁,也就是说没有机会使用 unique_ptr 来访问无效的数据,换句话来说,这种赋值是不会出现任何问题的,即没有理由禁止这种赋值。实际上,编译器确实允许这种赋值,这正是unique_ptr更聪明的地方。
C++有一个标准库函数std::move(),能够将一个unique_ptr赋给另一个。下面是一个使用前述demo()函数的例子,该函数返回一个unique_ptr对象,使用move后,原来的指针仍转让所有权变成空指针,可以对其重新赋值。
unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;
shared_ptr
是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:
(1)shared_ptr
对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
(2)时间上的开销主要在初始化和拷贝操作上, *和->操作符重载的开销跟auto_ptr是一样;
(3)开销并不是我们不使用shared_ptr的理由,,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。
1.基本使用与boost里shared_ptr的使用方式一致
void Test_Shared_ptr()
{
std::shared_ptr<int> sp1(new int(20));
std::shared_ptr<int> sp2(sp1);
std::cout << *sp2 << std::endl;
}
2.由于C++11里面没有shared_array或scoped_array之类智能指针,所以只能用shared_ptr需要自己定制删除器:
(1)首先自己定制删除器(例如我定制了一个delete和一个delete[ ])
自己需要编写相应的仿函数,在用shared_ptr时不用将自己编写的删除器作为模板参数,但是在构造shared_ptr对象时需要传相应仿函数的对象。
template<class T>
struct Delete
{
void operator()(T* ptr)
{
delete ptr;
}
};
template<class T>
struct DeleteArray
{
void operator()(T* ptr)
{
delete[] ptr;
}
};
(2)使用:
void Test_Shared_ptr()
{
std::shared_ptr<int> sp1(new int(20));
std::shared_ptr<int> sp2(sp1);
std::cout << *sp2 << std::endl;
DeleteArray<std::string> da;
std::shared_ptr<std::string> sp3(new std::string[10],da);
}
weak_ptr
被设计为与 shared_ptr
共同工作,可以从一个 shared_ptr
或者另一个 weak_ptr
对象构造而来。weak_ptr
是为了配合 shared_ptr
而引入的一种智能指针,它更像是 shared_ptr
的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,因此取名为weak,表明其是功能较弱的智能指针。它的最大作用在于协助shared_ptr工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。
使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr管理的对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。总结来说,weak_ptr的基本用法总结如下:
weak_ptr<T> w; //创建空weak_ptr,可以指向类型为T的对象。
weak_ptr<T> w(sp); //与shared_ptr指向相同的对象,shared_ptr引用计数不变。T必须能转换为sp指向的类型。
w=p; //p可以是shared_ptr或weak_ptr,赋值后w与p共享对象。
w.reset(); //将w置空。
w.use_count(); //返回与w共享对象的shared_ptr的数量。
w.expired(); //若w.use_count()为0,返回true,否则返回false。
w.lock(); //如果expired()为true,返回一个空shared_ptr,否则返回非空shared_ptr。
#include < assert.h>
#include
#include
#include
using namespace std;
int main()
{
shared_ptr<int> sp(new int(10));
assert(sp.use_count() == 1);
weak_ptr<int> wp(sp); //从shared_ptr创建weak_ptr
assert(wp.use_count() == 1);
if (!wp.expired())//判断weak_ptr观察的对象是否失效
{
shared_ptr<int> sp2 = wp.lock();//获得一个shared_ptr
*sp2 = 100;
assert(wp.use_count() == 2);
}
assert(wp.use_count()== 1);
cout<<"int:"<<*sp<<endl;
return 0;
}
weak_ptr的作用:其实weak_ptr可用于打破循环引用。引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。
#include
#include
class Woman;
class Man
{
private:
//std::weak_ptr _wife;
std::shared_ptr<Woman> _wife;
public:
void setWife(std::shared_ptr<Woman> woman)
{
_wife = woman;
}
void doSomthing()
{
if(_wife.lock())
{
}
}
~Man()
{
std::cout << "kill man\n";
}
};
class Woman
{
private:
//std::weak_ptr _husband;
std::shared_ptr<Man> _husband;
public:
void setHusband(std::shared_ptr<Man> man)
{
_husband = man;
}
~Woman()
{
std::cout <<"kill woman\n";
}
};
int main(int argc, char** argv)
{
std::shared_ptr<Man> m(new Man());
std::shared_ptr<Woman> w(new Woman());
if(m && w)
{
m->setWife(w);
w->setHusband(m);
}
return 0;
}
在Man类内部会引用一个Woman,Woman类内部也引用一个Man。当一个man和一个woman是夫妻的时候,他们直接就存在了相互引用问题。man内部有个用于管理wife生命期的shared_ptr变量,也就是说wife必定是在husband去世之后才能去世。同样的,woman内部也有一个管理husband生命期的shared_ptr变量,也就是说husband必须在wife去世之后才能去世。这就是循环引用存在的问题:husband的生命期由wife的生命期决定,wife的生命期由husband的生命期决定,最后两人都死不掉,违反了自然规律,导致了内存泄漏。
一般来讲,解除这种循环引用有下面三种可行的方法:
(1)当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
(2)当parent的生存期超过children的生存期的时候,children改为使用一个普通指针指向parent。
(3)使用弱引用的智能指针打破这种循环引用。
weak_ptr对象引用资源时不会增加引用计数,但是它能够通过lock()方法来判断它所管理的资源是否被释放。做法就是上面的代码注释的地方取消注释,取消Woman类或者Man类的任意一个即可,也可同时取消注释,全部换成弱引用weak_ptr。
另外很自然地一个问题是:既然weak_ptr不增加资源的引用计数,那么在使用weak_ptr对象的时候,资源被突然释放了怎么办呢?呵呵,答案是你根本不能直接通过weak_ptr来访问资源。那么如何通过weak_ptr来间接访问资源呢?答案是:在需要访问资源的时候weak_ptr为你生成一个shared_ptr,shared_ptr能够保证在shared_ptr没有被释放之前,其所管理的资源是不会被释放的。创建shared_ptr的方法就是lock()方法。
另外很自然地一个问题是:既然weak_ptr不增加资源的引用计数,那么在使用weak_ptr对象的时候,资源被突然释放了怎么办呢?呵呵,答案是你根本不能直接通过weak_ptr来访问资源。那么如何通过weak_ptr来间接访问资源呢?答案是:在需要访问资源的时候weak_ptr为你生成一个shared_ptr,shared_ptr能够保证在shared_ptr没有被释放之前,其所管理的资源是不会被释放的。创建shared_ptr的方法就是lock()方法。
注意:shared_ptr实现了operator bool() const方法来判断一个管理的资源是否被释放。
boost中有scoped_ptr
、scoped_array
、shared_ptr
、shared_array
和weak_ptr
这5种类型。
1.源代码:
template<class T>
class scoped_ptr
{
private:
T * px; //只含有一个成员变量T*的指针
//将拷贝构造与赋值运算符重载声明为私有,且不实现
scoped_ptr(scoped_ptr const &);
scoped_ptr & operator=(scoped_ptr const &);
typedef scoped_ptr<T> this_type;
void operator==( scoped_ptr const& ) const;
void operator!=( scoped_ptr const& ) const;
public:
typedef T element_type;
//构造函数
explicit scoped_ptr( T * p = 0 )
: px( p )
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_constructor_hook( px );
#endif
}
#ifndef BOOST_NO_AUTO_PTR
explicit scoped_ptr( std::auto_ptr<T> p ) BOOST_NOEXCEPT
: px( p.release() )
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_constructor_hook( px );
#endif
}
#endif
//析构函数
~scoped_ptr()
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_destructor_hook( px );
#endif
boost::checked_delete( px );
}
void reset(T * p = 0)
{
BOOST_ASSERT( p == 0 || p != px );
this_type(p).swap(*this);
}
T & operator*() const
{
BOOST_ASSERT( px != 0 );
return *px;
}
T * operator->() const
{
BOOST_ASSERT( px != 0 );
return px;
}
//获得原生指针
T * get() const BOOST_NOEXCEPT
{
return px;
}
#include
void swap(scoped_ptr & b) BOOST_NOEXCEPT
{
T * tmp = b.px;
b.px = px;
px = tmp;
}
2.使用:
#include
#include
#include
#include
int main()
{
boost::scoped_ptr<int> sp1(new int(10));
//boost::scoped_ptr sp2(sp1); //不能拷贝
boost::scoped_array<std::string> sp2(new std::string[10]);
return 0;
}
scoped_ptr:scoped_ptr和auto_ptr的特点完全一样。虽然说socped_ptr不能赋值拷贝,但也有用处,但另一缺点是它不能管理数组,很多情况我们都需要在函数内部动态申请内存,等函数返回时释放内存。
socped_array的特点就在于此,它可以管理连续的地址空间,而在离开作用域时自动释放。需要注意的是socped_array仍然不能用于容器或者函数间传递,因为它仍然没有实现计数引用。
1.使用:
void Test_boost_shared_ptr()
{
boost::shared_ptr<int> sp1(new int(10));
std::cout << *sp1 << std::endl; //输出10
boost::shared_ptr<int> sp2(sp1);
std::cout << *sp2 << std::endl; //输出10
boost::shared_array<std::string> sp3(new std::string[10]);
sp3[5] = "111"; //shared_array里面重载了[],所以可以采用下标的方式进行读写
boost::shared_array<std::string> sp4(sp3);
std::cout << sp4[5] << std::endl; //输出111
}
较之于socped_ptr,shared_ptr的特点在于它可以赋值拷贝,内部有一个引用计数器,只有当计数器等于0时才析构内存,它内部重载了=运算符,所以说shared_ptr可用做容器元素,正如名字一样,shared_ptr——共享。
shared_array
:可以这么说,shared_array = socped_arrayr + shared_ptr,怎么说呢。。。就是说shared_ptr既可以用来管理连续地址空间,又可以在函数见传递,或者是用于容器中。
week_ptr
:shared_ptr的引用技术很好的解决了复制拷贝问题,但是这些都是01问题,要么能拷贝复制,要么不能拷贝复制,那假如我想在有些情况下需要拷贝复制,有些情况下不需要拷贝复制呢?比如说循环引用,再比如说基类中的指针总不能用计数器吧。所以说这就是week_ptr的用处,week_ptr可以对shared_ptr进行引用而不会引起其计数器增加。
对于智能指针对象本身而言,其不是线程安全性的,底层没有提供加锁机制,但是其可以用来作为线程的安全性检测:
它的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr
有两个数据成员,读写操作不能原子化。
shared_ptr/weak_ptr
的线程安全级别与 std::string
和STL 容器一样,都不是线程安全的。
对于 weak_ptr
在提升为 shared_ptr
的时候是线程安全的。
当一个对象暴漏给多线程时,如何保证正确析构?使用 weak_ptr
保存对象就可以解决竞态条件
技术陷阱:
shared_ptr 技术与陷阱
意外延长对象的生命期
shared_ptr
是强引用(“铁丝”绑的),只要有一个指向x 对象的 shared_ptr
存在,该对象就不会析构。而 shared_ptr
又是允许拷贝构造和赋值的(否则引用计数就无意义了),如果不小心遗留了一个拷贝,那么对象就永世长存了。例如前面提到如果把p. 16 中L48 observers_ 的类型改为vector
另外一个出错的可能是boost::bind,因为boost::bind 会把实参拷贝一份,如果参数是个shared_ptr,那么对象的生命期就不会短于boost::function 对象:
class Foo
{
void doit();
};
shared_ptr<Foo> pFoo(new Foo);
boost::function<void()> func = boost::bind(&Foo::doit, pFoo); // long life foo
这里func 对象持有了shared_ptr 的一份拷贝,有可能会在不经意间延长倒数第二行创建的Foo 对象的生命期。
函数参数
因为要修改引用计数(而且拷贝的时候通常要加锁), shared_ptr
的拷贝开销比拷贝原始指针要高,但是需要拷贝的时候并不多。多数情况下它可以以const reference 方式传递,一个线程只需要在最外层函数有一个实体对象,之后都可以用const reference 来使用这个shared_ptr。例如有几个函数都要用到Foo 对象:
void save(const shared_ptr<Foo>& pFoo); // pass by const reference
void validateAccount(const Foo& foo);
bool validate(const shared_ptr<Foo>& pFoo) // pass by const reference
{
validateAccount(*pFoo);
// ...
}
那么在通常情况下,我们可以传常引用(pass by const reference):
void onMessage(const string& msg)
{
shared_ptr<Foo> pFoo(new Foo(msg)); // 只要在最外层持有一个实体,安全不成问题
if (validate(pFoo))
{ // 没有拷贝pFoo
save(pFoo); // 没有拷贝pFoo
}
}
遵照这个规则,基本上不会遇到反复拷贝 shared_ptr
导致的性能问题。另外由于pFoo 是栈上对象,不可能被别的线程看到,那么读取始终是线程安全的.
析构动作在创建时被捕获
这是一个非常有用的特性,这意味着:
•虚析构不再是必需的。
•shared_ptr 可以持有任何对象,而且能安全地释放。
•shared_ptr
对象可以安全地跨越模块边界,比如从DLL 里返回,而不会造成
从模块A 分配的内存在模块B 里被释放这种错误。
•二进制兼容性,即便Foo 对象的大小变了,那么旧的客户代码仍然可以使用新
的动态库,而无须重新编译。前提是Foo 的头文件中不出现访问对象的成员的
inline 函数,并且Foo 对象的由动态库中的Factory 构造,返回其shared_ptr。
•析构动作可以定制。
最后这个特性的实现比较巧妙,因为shared_ptr 只有一个模板参数,而“析构行为”可以是函数指针、仿函数(functor)或者其他什么东西。这是泛型编程和面向对象编程的一次完美结合
析构所在的线程对象的析构是同步的,当最后一个指向x 的shared_ptr
离开
其作用域的时候,x 会同时在同一个线程析构。这个线程不一定是对象诞生的线程。这个特性是把双刃剑:如果对象的析构比较耗时,那么可能会拖慢关键线程的速度(如果最后一个 shared_ptr
引发的析构发生在关键线程);同时,我们可以用一个单独的线程来专门做析构,通过一个 BlockingQueue
把对象的析构都转移到那个专用线程,从而解放关键线程。
shared_ptr
是管理共享资源的利器,需要注意避免循环引用,通常的做法是owner 持有指向child 的shared_ptr,child 持有指向owner 的weak_ptr。
综上:智能指针shared_ptr本身不是线程安全的,当他暴漏给多线程时,读(智能指针对象之间的拷贝)写(对象的析构)仍然需要加锁来保证安全性(一般使用互斥锁就够了,无需读写锁,因为临界区非常小,通常只有一个语句(赋值等等)),但是对于它的内部的引用计数的操作以及由weak_ptr提升为shared_ptr时却是线程安全的。
解决循环引用方法:使用者保存shared_ptr,在child(类中)使用weak_ptr封装指针。
1、如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:
2、如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。
智能指针的交叉(循环)引用问题?造成什么结果?怎么解决?
循环引用造成了资源无法释放