此篇均参考C++ Primer第五版
为了更容易更安全的使用动态内存,C++11提供了两种智能指针,来管理动态对象。智能指针行为类似于常规指针,重要的区别在于它负责自动释放所指向的对象。shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占“所指向的对象。weak_ptr是一种若引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。
智能指针也是模板,当我们创建一个智能指针时,必须提供额外的信息--指针可以指向的类型,在尖括号内给出类型。
shared_ptr p1; //可以指向string 空指针
shared_ptr> p2; //可以指向int的list
默认初始化的智能指针保存着一个空指针。
指针指针的使用方式于普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空。
//如果p1不为空,检查它是否指向一个空string
if(p1 && p1->empty())
*p1="hi";
shared_ptr和unique_ptr都支持的操作:
shared_ptr独有的操作:
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。
//指向一个值为42的int的shared_ptr
shared_ptr p3=make_shared(42);
//p4指向一个值为"999999999"的string
shared_ptr p4=make_shared("999999999");
//p5指向一个值初始化的int 即,值为0
shared_ptr p5=make_shared();
可以看出make_shared用其参数来构造给定类型的对象。
通常用auto定义一个对象来保存make_shared的结果
auto p6=make_shared>();
当进行拷贝或者赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。
auto p=make_shared(42); //p指向的对象只有p一个引用者
auto q(p); //p和q指向相同的对象,此对象有两个引用者
每个shared_ptr都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。
计数器递增的情况:
计数器递减的情况:
一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
auto r = make_shared(42);//r指向的int只有一个引用者
r=q; //给r赋值,令它指向另一个地址
//递增q指向的对象的引用计数
//递减r原来指向的对象的引用计数
//r原来指向的对象已没有引用者,会自动释放
来看一段程序:
vector v1; //空vector
{ //新作用域
vector v2={"a","an","the"};
v1=v2; //从v2拷贝元素到v1中
} //v2被销毁 其中元素也被销毁
//v1有三个元素,是原来v2中元素的拷贝。
这段程序中,每个vector拥有其自己的元素,当我们从v2中拷贝元素到v1时,v1和v2中的元素是相互分离的。由一个vector分配的元素只有当这个vector存在时才存在。当一个vector被销毁时,这个vector中的元素也都被销毁。
我们现在实现一个strBlob类,strBlob对象不同的拷贝之间共享相同的元素。即,当我们拷贝一个strBlob时,原strBlob对象及其拷贝应该引用相同的底层元素,当某个对象被销毁时,我们不能单方面的销毁底层元素。
为了实现我们所希望的数据共享,我们为每个strBlob设置一个shared_ptr来管理动态分配的vector.此shared_ptr的成员将记录有多少个strBlob共享相同的vector,并在vector的最后一个使用者被销毁时释放vector。
strBlob的实现如下:
//实现两个strBlob指向相同的底层元素,一个strBlob对象销毁时 不能单方面的销毁底层元素
class strBlob {
public:
strBlob();//默认构造函数
strBlob(initializer_list il);
strBlob(vector str);
int size() const{ return data->size(); } //定义在类内部的函数是内联函数
bool empty() const { return data->empty(); }
//添加和删除元素
void push_back(const string& t) { data->push_back(t); }
void pop_back();
//元素访问
string& front();
string& back();
private:
shared_ptr> data; //shared_ptr是一个模板类 ,data可以指向一个 vector
void check(int i, const string& msg) const; //常量成员函数 常量对象可以使用
};
strBlob::strBlob() :data(make_shared>()) { //默认初始化一个空的智能指针
cout << "默认构造函数" << endl;
}
strBlob::strBlob(initializer_list il) : data(make_shared>(il)) {
cout << "列表初始化构造函数" << endl;
}
strBlob::strBlob(vector str) : data(make_shared>(str)) {
cout<<"模板构造函数" << endl;
}
void strBlob::check(int i, const string& msg) const {
if (i >= data->size())
throw out_of_range(msg);
}
void strBlob::pop_back() {
//如果vector为空 会抛出一个异常
//也可以不需要check函数 只要保证正确操作 或者使用pop之前检测vector是否为空
check(0, "pop_back on empty strBlob");
data->pop_back();
}
string& strBlob::front() {
check(0, "pop_back on empty strBlob");
return data->front();
}
string& strBlob::back() {
check(0, "pop_back on empty strBlob");
return data->back();
}
main( )中程序如下:
int main()
{
strBlob b1 = strBlob({ "hello","china","jiangXi" }); //调用列表构造函数
cout << b1.size() << endl; //size=3
{
strBlob b2 = b1; //b1,b2引用相同的底层元素
b2.push_back("jiuJiang");
}//b2被销毁 b2所指向的底层元素没有被销毁
cout << b1.size() << endl; //size=4
cout << "//********************//" << endl;
vector str1;
{
vector str2 = { "a","b","c" };
str1 = str2;
cout << str1.size() << endl;
str2.push_back("d");
}
cout << str1.size() << endl;
system("pause");
return 0;
}
从执行结果也能看出b1,b2指向的是相同的底层元素,str1,str2指向不同底层元素。
new的使用可看我另外一篇博客
用new返回的指针来初始化一个智能指针
shared_ptr p(new int(42)); //p指向一个值为42的int
C++11之后的智能指针的构造函数都有explict关键词修饰,因此不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式。
shared_ptr p1 = new int(1024);//错误:必须使用直接初始化形式
shared_ptr p2(new int(1024));//正确:使用了直接初始化形式
智能指针默认使用delete释放它关联的对象,但我们可以提供自己的操作来代替delete。
如果混合使用的话,智能指针自动释放之后,普通指针就会变成悬空指针,如果试图使用这样的普通指针,其行为是未定义的。当将一个shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr。一旦这样做了,我们就不应该再使用内置指针来访问shared_ptr所指向的内存了。
与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定的对象。当unique_ptr被销毁时,它所指向的对象也被销毁。
当定义一个unique_ptr时,需要将其绑定到一个new返回的指针(必须直接初始化)
unique_ptr p1(new int(42));
C++ 14 为unique_ptr提供了类似于make_shared的标准库函数,make_unique 创建并返回 unique_ptr 至指定类型的对象。
auto p2=make_unique(42);
unique_ptr p1(new string("Stegosaurus"));
unique_ptr p1(p2); //错误 unique_ptr不支持拷贝
unique_ptr p3;
p3=p2; //错误 unique_ptr不支持赋值
//将所有权从p1(指向string Stegosaurus)转移给p2
unique_ptr p2(p1.release());//release将p1置为空
unique_ptrp3(new string("Trex"));
//将所有权从p3转移到p2
p2.reset(p3.release());//reset释放了p2原来指向的内存
release 成员返回unique_ptr当前保存的指针并将其置为空。因此,p2被初始化为 p1原来保存的指针,而p1被置为空。
reset成员接受一个可选的指针参数,令 unique_ptr 重新指向给定的指针。如果 unique ptr不为空,它原来指向的对象被释放。因此,对p2调用reset释放了"stegosaurus"初始化的string所使用的内存,将p3对指针的所有权转移给p2,并将p3置为空。
调用 release 会切断 unique_ptr和它原来管理的对象间的联系。release 返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。在本例中,管理内存的责任简单地从一个智能指针转移给另一个。但是,如果我们不用另一个智能指针来保存 release 返回的指针,我们的程序就要负责资源的释放:
p2.release(); //错误:p2不会释放内存,而且我们丢失了指针
auto p=p2.release(); //正确,但我们必须记得delete(p)