标准库中的智能指针shared_ptr

2023-02-22,,

  智能指针的出现是为了能够更加方便的解决动态内存的管理问题。注:曾经记得有本书上说可以通过vector来实现动态分配的内存的自动管理,但是经过试验,在gcc4.8.5下是不行的。这个是容易理解的,vector是个模板,它不能辨别传入的数据类型是否是指针,从而也不能进行自动的释放内存操作。如果对非new出的对象进行delete操作,反而还会引起一些不必要的问题。

  C++11标准库为了能够使程序员能够更安全的使用动态内存,提供了两种智能指针类型来管理动态对象。

shared_ptr类

  智能指针也是模板,所以当我们创建一个智能指针时也需要提供额外的信息——指针可以指向的类型。例如:

  shared_ptr<string> sp1;  //sp1是这个智能指针的名字,尖括号里的string表示这个智能指针指向的是一个string类型的变量(记住,虽然这里没有我们熟悉的  * ,但sp1是一个指针),默认初始化的智能指针中保存者一个空指针。

  智能指针的使用和普通指针一致,解引用一个指针返回它所指向的对象,如果在一个条件判断中使用智能指针效果就是检测它是否为空。

shared_ptr<string> sp1;
if(sp1)
{
std::cout << "判断智能指针sp1是否为空指针" << std::endl;
}
if(sp1!= nullptr )
{
if(sp1->empty())
{
     *sp1 = "Hi Smart Pointer!";    //给该智能指针指向的字符串赋值
std::cout << "判断该智能指针指向的字符串是否为空!" << std::endl;
}
}

  shared_ptr和unique_ptr都支持的操作:

shared_ptr<T> sp / unique_ptr<T> up 声明一个智能指针,默认初始化时,该智能指针中保存着一个空指针
p  将p作为一个条件判断,若p指向一个对象,则为true(和普通指针一样,就是判断指针本身是否为nullptr)
*p 解引用p,获得它指向的对象。和普通指针的*p语义一样。
p->mem 等价于(*p).mem
p.get() 返回p中保存的指针,但是这个方法使用起来要小心,如果智能指针释放了其对象,返回的指针所指向的对象也就消失了
swap(p.q) 交换p和q中的指针
p.swap(q) 和swap(p.q)的作用是一样的

  shared_ptr独有的操作

make_shared<T>(args) args:这种表达在C++primer里就是参数列表的意思。make_shared<T>(args)返回一个shared_ptr指针,指向一个动态分配的类型为T的对象,使用args来初始化此对象(相当于是给构造函数传参)
shared_ptr<T>p(q) p是shared_ptr q的拷贝,此操作会递增q中的引用计数器。q中的指针必须要能转换成T*
p = q p和q都是shared_ptr,所保存的指针必须能够相互转换,此操作会递减p的引用计数,递增q的引用计数;若p的一用计数变为0,则将其管理的原内存释放
p.unique()  若p.use_count()为1则返回true;否则返回false
p.use_count() 返回与p共享对象的智能指针的数量;可能很慢,主要用于调试。

  shared_ptr和unique_ptr的区别:shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向的对象。weak_ptr是一种伴随类,是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在头文件memory中。

  示例:

#include <iostream>
#include <string>
#include <memory> using namespace std;
int main(int argc,char *argv[])
{
shared_ptr<int> sp1;
if(nullptr == sp1)
{
std::cout << "shared_ptr默认初始化时,其内保存着一个空指针" << std::endl;
}
if(sp1.use_count() == )
{
std::cout << "默认初始化时,这个智能指针的引用计数值是:" << sp1.use_count() << std::endl;
} shared_ptr<int> sp2 = make_shared<int>(); //声明一个指向int类型的智能指针,并将其初始化为42,sp2这个智能指针的引用计数值应该是1
std::cout << "这个智能指针内保存的值是:" << *sp2 << ",它的引用计数器的值是:" << sp2.use_count() << std::endl;
sp1 = sp2; //无论何时,拷贝一个shared_ptr,它的引用计数器都会递增,例如将用一个shared_ptr初始化另外一个shared_ptr,或者将它作为参数传递给一个函数,以及作为函数的返回值
//当我们给shared_ptr赋予了一个新值或者是shared_ptr被销毁,它所关联的引用计数器就会递减,一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象.
std::cout << "sp1的引用计数值是:" << sp1.use_count() << ",sp2的引用计数值是:" << sp2.use_count() << std::endl;
shared_ptr<int> sp3 = make_shared<int>();
sp2 = sp3; //给sp2赋予一个新的值,这里sp2里关联到原来指向的那个对象的引用计数器值会递减,但同时,它又被指向了新的对象,这个关联到新对象的引用计数值又会增加。
std::cout << "sp2关联的计数器值是:" << sp2.use_count() << ",sp3关联的引用计数器值是:" << sp3.use_count() << "sp1关联的应用计数器值是:" << sp1.use_count() << std::endl;
//当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象,通过析构函数完成此工作。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,则shared_ptr
//的析构函数就会销毁对象,并释放它占用的内存, {
shared_ptr<int> sp4 = make_shared<int>();
sp1 = sp4;
}
if(sp1!= nullptr)
{
std::cout << "上面的sp4虽然被销毁了,但是由于有sp1=sp4这个赋值操作,导致指向sp4原本指向的对象的指针对于0个,那么它申请的内存就不会随着sp4的析构而销毁。" << "sp1当前所保存的值是: " << *sp1 << std::endl;
}
//shared_ptr在无用之后仍然保留的一种情况是,你将shared_ptr存放在一个容器中,随后重排了容器,从而不再需要某些元素,在这种情况下,你应该确保erase删除那些不需要的shared_ptr元素。
return ();
}

  分配动态内存的几个理由:

  1.不知道自己想要多大的空间;

  2.不知道对象的类型是什么,(void *)

  3.需要多个对象共享数据

  程序非自由空间被耗尽的情况还是有可能发生的,一旦一个程序用光了它所有可用的内存,new表达式就会失败。默认情况下,如果new不能分配所要求的的内存空间,它会抛出一个类型为bad_alloc的异常。可以改变使用new的方式来阻止它抛出异常。

int *p2 = new (nothrow) int; //此时如果分配内存失败,那么new就会返回一个空指针,这种形式的new称为定位new,定位new表达式允许我们向new传递额外参数。这个例子中传入的是一个由标准库定义的名为nothrow的对象,意思就是不抛出异常。这个都定义在头文件new中

  new运算符包含两个动作:分配内存,构造对象。

  delete运算符负责释放new运算符分配的内存,把它还给操作系统,delete也包含两个动作,销毁给定的指针指向的对象,释放对应的内存。

  传给delete的指针必须是动态分配的内存或者是一个空指针,释放一块并非new分配的内存,或者将相同的指针释放多次,其行为是未定义的。

  动态对象的生存期直到被释放时为止。(new/delete  new出来的指针被称为内置指针)

动态内存的管理里容易出现的几个问题:

1.忘记delete内存。忘记释放动态内存常会导致“内存泄漏”问题,因为这种内存永远不可能归还给自由空间了。

2.使用已经释放掉的对象。通过在释放内存后将指针置为空,有时可以检测出这种错误。如果不置为空,那么会产生空悬指针(野指针)。这会造成灾难性的后果。

3.同一块内存释放两次,当有两个指针指向相同的动态分配对象时,可能发生这种错误。如果对其中一个指针进行了delete操作,对象的内存就被归还给自由空间了,如果随后又对第二个指针进行delete操作,自由空间就可能被破坏。

shared_ptr和new的结合使用

  主要用于shared_ptr的初始化。使用示例如下:

shared_ptr<int> sp1 = new int();            //这是错误的
shared_ptr<int> sp2(new int()); //这才是正确的写法

  默认情况下,一个用来初始化智能指针的普通指针必须要指向动态内存,因为智能指针默认使用delete来释放它所关联的对象。

shared_ptr<T> p(q) p管理内置指针q所指向的对象,q必须指向new分配的内存,且能够转换为T*
shared_ptr<T> p(u) p从unqiue_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置为空
p.reset(q,d) 传递了内置指针q和可调用对象d,那么会使用d来替代delete

  不要使用get初始化另外一个智能指针或为智能指针赋值,get是用来给那些只能使用内置指针的地方来用的。

  在使用reset之前,我们要检查自己是不是当前对象仅有的用户,如果不是,那么在使用之前要制作一份新的拷贝。函数退出的两种情况:正常退出和发生异常。无论哪种情况,局部对象都会被销毁。

智能指针使用规范:

不使用相同的内置指针值初始化或reset多个智能指针
不delete get返回的指针
不使用get()初始化或reset另一个智能指针
如果你使用了get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就无效了
如果你使用智能指针管理的资源不是new分配内存,记住传递给它一个删除器。

标准库中的智能指针shared_ptr的相关教程结束。

《标准库中的智能指针shared_ptr.doc》

下载本文的Word格式文档,以方便收藏与打印。