C++智能指针

2023-05-16

  • 参考:
  • 头文件
  • auto_ptr
    • 使用示例
  • unique_ptr
    • 类模板声明
    • 示例
  • shared_ptr
    • 模板声明
    • 通过辅助类模拟实现 shared_ptr
    • shared_ptr使用示例
  • weaked_ptr
    • weak_ptr 用法
    • weak_ptr 的作用
  • 如何选择智能指针

C++ 标准模板库 STL(Standard Template Library) 一共给我们提供了四种智能指针:

  • auto_ptr(C++11已舍弃)
  • unique_ptr
  • shared_ptr
  • weak_ptr

其中 auto_ptr 是 C++98 提出的,C++11 已将其摒弃,并提出了 unique_ptr 替代 auto_ptr。 虽然 auto_ptr 已被摒弃,但在实际项目中仍可使用,但建议使用更加安全的 unique_ptr,
shared_ptr 和 weak_ptr 则是 C+11 从准标准库 Boost 中引入的两种智能指针

参考:

C++ STL 四种智能指针

C++ 智能指针详解

头文件

 #include<memory>

auto_ptr

C++11中,auto_ptr 已被摒弃
std::auto_ptr 可用来管理单个对象的堆内存,但是,请注意如下几点:

(1) 尽量不要使用“operator=”。如果使用了,请不要再使用先前对象。

(2) 记住 release() 函数不会释放对象,仅仅归还所有权。

(3) std::auto_ptr 最好不要当成参数传递。

(4) 由于 std::auto_ptr 的“operator=”问题,有其管理的对象不能放入 std::vector 等容器中。

(5) 不能用来管理堆内存数组

使用示例

// auto_ptr example
#include <iostream>
#include <memory>

int main () {
  std::auto_ptr<int> p1 (new int);
  *p1.get()=10;

  std::auto_ptr<int> p2 (p1);

  std::cout << "p2 points to " << *p2 << '\n';
  // (p1 is now null-pointer auto_ptr)

  return 0;
}

unique_ptr

类模板声明

non-specialized	
template <class T, class D = default_delete<T>> class unique_ptr;
array specialization	
template <class T, class D> class unique_ptr<T[],D>;
  • 为了避免因潜在的内存问题导致程序崩溃,摒弃 auto_ptr,提出了unique_ptr。
    unique_ptr 比 auto_ptr 更加安全,因为 auto_ptr 有拷贝语义,拷贝后原对象变得无效,再次访问原对象时会导致程序崩溃;
    unique_ptr 则禁止了拷贝语义,但提供了移动语义,即可以使用std::move() 进行控制权限的转移
unique_ptr<string> upt(new string("lvlv"));
unique_ptr<string> upt1(upt);	//编译出错,已禁止拷贝
unique_ptr<string> upt1=upt;	//编译出错,已禁止拷贝
unique_ptr<string> upt1=std::move(upt);  //控制权限转移

auto_ptr<string> apt(new string("lvlv"));
auto_ptr<string> apt1(apt);	//编译通过
auto_ptr<string> apt1=apt;	//编译通过

这里要注意,在使用std::move将unique_ptr的控制权限转移后,不能够再通过unique_ptr来访问和控制资源了,否则同样会出现程序崩溃。我们可以在使用unique_ptr访问资源前,使用成员函数get()进行判空操作。

unique_ptr<string> upt1=std::move(upt);  	//控制权限转移
if(upt.get()!=nullptr)						//判空操作更安全
{
	//do something
}

  • unique_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 来访问无效的数据,换句话来说,这种赋值是不会出现任何问题的,即没有理由禁止这种赋值。实际上,编译器确实允许这种赋值。相对于 auto_ptr 任何情况下都允许拷贝语义,这正是 unique_ptr 更加灵活聪明的地方。

下图演示了两个 unique_ptr 实例之间的所有权转换。
在这里插入图片描述
unique_ptr 指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过 reset 方法重新指定、通过 release 方法释放所有权、通过移动语义转移所有权,unique_ptr 还可能没有对象,这种情况被称为 empty。
条款21:尽量使用std::make_unique和std::make_shared而不直接使用new
智能指针之make_unique与make_shared

让我们从对齐std::make_unique 和 std::make_shared这两块开始。std::make_shared是c++11的一部分,但很可惜std::make_unique不是。它是在c++14里加入标准库的。假如你在使用c++11,也别担心,你很容易写出一个基本的版本。看这里:

template<typename T, typename… Ts>
std::unique_ptr make_unique(Ts&&… params)
{
return std::unique_ptr(new T(std::forward(params)…));
}
正如你看到的,make_unique完美传递了参数给对象的构造函数,从一个原始指针构造出一个std::unique_ptr,返回创建的std::unique_ptr。
这个形式的函数不支持数组和定制删除器(见条款18),但它证明了一点点的努力就可以根据需要创建一个make_unique。要记住的是不要把你的版本放到std命名空间里,因为你不想当升级到c++14后会和库提供的标准实现冲突吧。
std::make_unique 和 std::make_shared是三个make函数中的两个,make函数用来把一个任意参数的集合完美转移给一个构造函数从而生成动态分配内存的对象,并返回一个指向那个对象的灵巧指针。第三个make是std::allocate_shared。它像std::make_shared一样,除了第一个参数是一个分配器对象,用来进行动态内存分配。

make_unique使用示例
auto upw1(std::make_unique< Widget >()); // with make func
std::unique_ptr< Widget > upw2(new Widget); // without make func

make_shared的使用示例
auto spw1(std::make_shared< Widget >()); // with make func
std::shared_ptr< Widget > spw2(new Widget); // without make func

unique_ptr的基本操作有:

//智能指针的创建
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()等价
  • 扩展 auto_ptr 不能完成的功能。
    (a)unique_ptr 可放在容器中,弥补了 auto_ptr 不能作为容器元素的缺点。
//方式一:
vector<unique_ptr<string>> vs { new string{“Doug”}, new string{“Adams”} };  

//方式二:
vector<unique_ptr<string>>v;  
unique_ptr<string> p1(new string("abc"));  

(b)管理动态数组,因为 unique_ptr 有 unique_ptr<X[]> 重载版本,销毁动态对象时调用 delete[]。

unique_ptr<int[]> p (new int[3]{1,2,3});  
p[0] = 0;// 重载了operator[]

(c)自定义资源删除操作(Deleter)。unique_ptr 默认的资源删除操作是 delete/delete[],若需要,可以进行自定义:

void end_connection(connection *p) { disconnect(*p); } //资源清理函数  

//资源清理器的“类型” 
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);// 传入函数名,会自动转换为函数指针  

示例

// unique_ptr::operator->
#include <iostream>
#include <memory>

struct C { int a; int b; };

int main () {
  std::unique_ptr<C> foo (new C);
  std::unique_ptr<C> bar;

  foo->a = 10;
  foo->b = 20;

  bar = std::move(foo);
  //unique_ptr重载了operator bool(),当unique_ptr存储的指针不是NULL,则返回true;
  //相当于调用了unique_ptr::get()!=nullptr,所以这里可以用if(foo)判断
  if (foo) std::cout << "foo: " << foo->a << ' ' << foo->b << '\n';
  if (bar) std::cout << "bar: " << bar->a << ' ' << bar->b << '\n';

  return 0;
}

shared_ptr

shared_ptr 是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象。shared_ptr 利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr 共同管理同一个对象。

注意:c++17前,std::shared_ptr它并不支持动态数组
shared_ptr和动态数组
std::tr1::shared_ptr源码赏析

模板声明

template <class T> class shared_ptr;

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:
(1)shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
(2)时间上的开销主要在初始化和拷贝操作上, * 和 -> 操作符重载的开销跟 auto_ptr 是一样;
(3)开销并不是我们不使用 shared_ptr 的理由,,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。

通过辅助类模拟实现 shared_ptr

(1)基础对象类
首先,我们来定义一个基础对象类 Point 类,为了方便后面我们验证智能指针是否有效,我们为 Point 类创建如下接口:

class Point
{
private:
    int x, y;
public:
    Point(int xVal = 0, int yVal = 0) :x(xVal), y(yVal) {}
    int getX() const { return x; }
    int getY() const { return y; }
    void setX(int xVal) { x = xVal; }
    void setY(int yVal) { y = yVal; }
};

(2)辅助类
在创建智能指针类之前,我们先创建一个辅助类。这个类的所有成员皆为私有类型,因为它不被普通用户所使用。为了只为智能指针使用,还需要把智能指针类声明为辅助类的友元。这个辅助类含有两个数据成员:计数count 与基础对象指针。也即辅助类用以封装使用计数与基础对象指针

class RefPtr
{
private:
    friend class SmartPtr;      
    RefPtr(Point *ptr):p(ptr),count(1){ }
    ~RefPtr(){delete p;}
    
    int count;   
    Point *p;                                                      
};

(3)为基础对象类实现智能指针类
引用计数是实现智能指针的一种通用方法。智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针。它的具体做法如下:
(a)当创建智能指针类的新对象时,初始化指针,并将引用计数设置为1;
(b)当能智能指针类对象作为另一个对象的副本时,拷贝构造函数复制副本的指向辅助类对象的指针,并增加辅助类对象对基础类对象的引用计数(加1);
(c)使用赋值操作符对一个智能指针类对象进行赋值时,处理复杂一点:先使左操作数的引用计数减 1(为何减 1:因为指针已经指向别的地方),如果减1后引用计数为 0,则释放指针所指对象内存。然后增加右操作数所指对象的引用计数(为何增加:因为此时做操作数指向对象即右操作数指向对象);
(d)完成析构函数:调用析构函数时,析构函数先使引用计数减 1,如果减至 0 则 delete 对象。

做好前面的准备后,我们可以为基础对象类 Point 书写一个智能指针类了。根据引用计数实现关键点,我们可以写出如下智能指针类:

class SmartPtr
{
public:
	
	//构造函数
	SmartPtr() { rp = nullptr; }
	SmartPtr(Point *ptr):rp(new RefPtr(ptr)) {}
	SmartPtr(const SmartPtr &sp):rp(sp.rp)
	{ 
		++rp->count;
		cout << "in copy constructor" <<endl;
	}
	
	//重载赋值运算符
	SmartPtr& operator=(const SmartPtr& rhs)
	{
		++rhs.rp->count;
		if (rp != nullptr && --rp->count == 0)
		{
			delete rp;
		}
		rp = rhs.rp;
		cout << "in assignment operator" << endl;
		return *this;
	}
	
	//重载->操作符
	Point* operator->()
	{
		return rp->p;
	}
	
	//重载*操作符
	Point& operator*()
	{
		return *(rp->p);
	}

	~SmartPtr()
	{
		if (--rp->count == 0)
			delete rp;
		else
			cout << "还有" << rp->count << "个指针指向基础对象" << endl;
	}

private:
	RefPtr* rp;
};

(4)智能指针类的使用与测试
至此,我们的智能指针类就完成了,我们可以来看看如何使用。

int main()
{
    //定义一个基础对象类指针
    Point *pa = new Point(10, 20);

    //定义三个智能指针类对象,对象都指向基础类对象 pa
    //使用花括号控制三个智能指针的生命周期,观察计数的变化
    {
        SmartPtr sptr1(pa);//此时计数 count=1
        cout <<"sptr1:"<<sptr1->getX()<<","<<sptr1->getY()<<endl;
        
        {
            SmartPtr sptr2(sptr1); //调用拷贝构造函数,此时计数为 count=2
            cout<<"sptr2:" <<sptr2->getX()<<","<<sptr2->getY()<<endl;
            
            {
            	SmartPtr sptr3;
                SmartPtr sptr3=sptr1; //调用赋值操作符,此时计数为 conut=3
                cout<<"sptr3:"<<(*sptr3).getX()<<","<<(*sptr3).getY()<<endl;
            }
            
            //此时count=2
        }
        
        //此时count=1;
    }
    
    //此时count=0;对象 pa 被 delete 掉
    cout << pa->getX() << endl;
    return 0;
}

运行结果:

sptr1:10,20
in copy constructor
sptr2:10,20
in assignment operator
sptr3:10,20
还有2个指针指向基础对象
还有1个指针指向基础对象
-572662307

如期,在离开大括号后,共享基础对象的指针从 3->2->1->0 变换,最后计数为 0 时,pa 对象被 delete,此时使用 getX() 已经获取不到原来的值。

(5)对智能指针的改进
目前这个智能指针只能用于管理 Point 类的基础对象,如果此时定义了个矩阵的基础对象类,那不是还得重新写一个属于矩阵类的智能指针类吗?但是矩阵类的智能指针类设计思想和 Poin t类一样啊,就不能借用吗?答案当然是能,那就是使用模板技术。为了使我们的智能指针适用于更多的基础对象类,我们有必要把智能指针类通过模板来实现。这里贴上上面的智能指针类的模板版本:

//模板类作为友元时要先有声明
template <typename T> class SmartPtr;
   
//辅助类
template <typename T> class RefPtr
{
private:
    //该类成员访问权限全部为private,因为不想让用户直接使用该类
    friend class SmartPtr<T>;  //定义智能指针类为友元,因为智能指针类需要直接操纵辅助类
    
    //构造函数的参数为基础对象的指针
    RefPtr(T *ptr):p(ptr), count(1)
    {}
    
    //析构函数
    ~RefPtr()
    {
    	delete p;
    }
    
    //引用计数
    int count;   
    
    //基础对象指针
    T *p;                                                      
};

//智能指针类
template <typename T> class SmartPtr
{
public:
	//构造函数
    SmartPtr(T *ptr) :rp(new RefPtr<T>(ptr))
    {}
    //拷贝构造函数
    SmartPtr(const SmartPtr<T> &sp):rp(sp.rp)
    {
    	++rp->count;
    }
    //重载赋值操作符
    SmartPtr& operator=(const SmartPtr<T>& rhs)
    {
        ++rhs.rp->count;        //首先将右操作数引用计数加1,
        if (--rp->count == 0)   //然后将引用计数减1,可以应对自赋值
            delete rp;
        rp = rhs.rp;
        return *this;
    }
    //重载*操作符
    T & operator *()
    {
        return *(rp->p);
    }
    //重载->操作符
    T* operator ->()
    {
        return rp->p;
    }
    //析构函数
    ~SmartPtr()
    {
        if (--rp->count == 0) //当引用计数减为0时,删除辅助类对象指针,从而删除基础对象
        {
        	delete rp;
        }
        else
        {
	        cout << "还有" << rp->count << "个指针指向基础对象" << endl;
	    }
    }
private:
    RefPtr<T> *rp;  //辅助类对象指针
};

shared_ptr使用示例

C++内存管理之shared_ptr

条款21:尽量使用std::make_unique和std::make_shared而不直接使用new

智能指针之make_unique与make_shared

让我们从对齐std::make_unique 和 std::make_shared这两块开始。std::make_shared是c++11的一部分,但很可惜std::make_unique不是。它是在c++14里加入标准库的。假如你在使用c++11,也别担心,你很容易写出一个基本的版本。看这里:

template<typename T, typename… Ts>
std::unique_ptr make_unique(Ts&&… params)
{
return std::unique_ptr(new T(std::forward(params)…));
}
正如你看到的,make_unique完美传递了参数给对象的构造函数,从一个原始指针构造出一个std::unique_ptr,返回创建的std::unique_ptr。
这个形式的函数不支持数组和定制删除器(见条款18),但它证明了一点点的努力就可以根据需要创建一个make_unique。要记住的是不要把你的版本放到std命名空间里,因为你不想当升级到c++14后会和库提供的标准实现冲突吧。
std::make_unique 和 std::make_shared是三个make函数中的两个,make函数用来把一个任意参数的集合完美转移给一个构造函数从而生成动态分配内存的对象,并返回一个指向那个对象的灵巧指针。第三个make是std::allocate_shared。它像std::make_shared一样,除了第一个参数是一个分配器对象,用来进行动态内存分配。

make_unique使用示例
auto upw1(std::make_unique< Widget >()); // with make func
std::unique_ptr< Widget > upw2(new Widget); // without make func

make_shared的使用示例
auto spw1(std::make_shared< Widget >()); // with make func
std::shared_ptr< Widget > spw2(new Widget); // without make func

  • 成员函数
函数名功能
T* get()返回指向被管理对象的裸指针,如果没有被管理的对象,则返回空指针
long use_count()返回shared_ptr指向的对象被共享的个数
bool unique() const返回是否是独占所有权( use_count 为 1)
void reset()放弃内部对象的所有权, 会引起原有对象的引用计数的减少
void reset(T* ptr)先释放当前被管理的对象的所有权,会引起原有对象的引用计数的减少。然后变更所有权为 ptr 指向的对象
void swap( shared_ptr& r )交换两个 shared_ptr 对象(即交换所拥有的对象)

示例一

//void swap( shared_ptr& r )
std::shared_ptr<int> sp0(new int(2));
std::shared_ptr<int> sp1(new int(11));
std::shared_ptr<int> sp2 = sp1;
printf("%d\n", *sp0);               // 2
printf("%d\n", *sp1);               // 11
printf("%d\n", *sp2);               // 11
sp1.swap(sp0);
printf("%d\n", *sp0);               // 11
printf("%d\n", *sp1);               // 2
printf("%d\n", *sp2);               // 11

//void reset()
std::shared_ptr<int> sp3(new int(22));
std::shared_ptr<int> sp4 = sp3;
printf("%d\n", *sp3);               // 22
printf("%d\n", *sp4);               // 22
sp3.reset();                        
printf("%d\n", sp3.use_count());    // 0
printf("%d\n", sp4.use_count());    // 1
printf("%d\n", sp3);                // 0
printf("%d\n", sp4);                // 指向所拥有对象的地址

//void reset(T* ptr) 
//long use_count()           
std::shared_ptr<int> sp5(new int(22));
std::shared_ptr<int> sp6 = sp5;
std::shared_ptr<int> sp7 = sp5;
printf("%d\n", *sp5);               // 22
printf("%d\n", *sp6);               // 22
printf("%d\n", *sp7);               // 22
printf("%d\n", sp5.use_count());    // 3
 printf("%d\n", sp6.use_count());    // 3
printf("%d\n", sp7.use_count());    // 3
sp5.reset(new int(33));                        
printf("%d\n", sp5.use_count());    // 1
printf("%d\n", sp6.use_count());    // 2
printf("%d\n", sp7.use_count());    // 2
printf("%d\n", *sp5);               // 33
printf("%d\n", *sp6);               // 22
printf("%d\n", *sp7);               // 22

示例二

// shared_ptr::operator->
#include <iostream>
#include <memory>

struct C { int a; int b; };

int main () {
  std::shared_ptr<C> foo;
  std::shared_ptr<C> bar (new C);

  foo = bar;

  foo->a = 10;
  bar->b = 20;
  std::cout<<foo.use_count()<<'\n';//返回与 foo 共享对象的 shared_ptr 的数量
  if (foo) std::cout << "foo: " << foo->a << ' ' << foo->b << '\n';
  if (bar) std::cout << "bar: " << bar->a << ' ' << bar->b << '\n';

  return 0;
}

结果

foo: 10 20
bar: 10 20

weaked_ptr

weak_ptr 被设计为与 shared_ptr 共同工作,可以从一个 shared_ptr 或者另一个 weak_ptr 对象构造而来。weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它更像是 shared_ptr 的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载 operator* 和 operator->,因此取名为 weak,表明其是功能较弱的智能指针。

它的最大作用在于协助 shared_ptr 工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着 weak_ptr 只对 shared_ptr 进行引用,而不改变其引用计数,当被观察的 shared_ptr 失效后,相应的 weak_ptr 也相应失效

weak_ptr 用法

  • weak_ptr 的成员函数 use_count() : 可以观测资源的引用计数;
  • 成员函数 expired() : 功能等价于 use_count()==0,但更快,表示被观测的资源(也就是 shared_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 <iostream>
#include <memory>
#include <string>
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;
}

程序输出:

int100

weak_ptr 的作用

现在要说的问题是,weak_ptr 到底有什么作用呢?
从上面那个例子看来,似乎没有任何作用。其实 weak_ptr 可用于打破循环引用
引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。一个简单的例子如下:

#include <iostream>
#include <memory>
  
class Woman;  
class Man
{  
private:  
    //std::weak_ptr<Woman> _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<Man> _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)使用弱引用的智能指针打破这种循环引用。

虽然这三种方法都可行,但方法 1 和方法 2 都需要程序员手动控制,麻烦且容易出错。

这里主要介绍一下第三种方法,使用弱引用的智能指针std:weak_ptr 来打破循环引用。

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() 成员函数。

注意: shared_ptr 实现了 operator bool() const 方法来判断被管理的资源是否已被释放。

operator bool () 提供一个本类型到bool的隐式转换,不允许使用参数。
关于operator bool () 和bool operator ==()

如何选择智能指针

上文简单地介绍了 C++ STL 的四种智能指针。当然,除了 STL 的智能指针,C++ 准标准库 Boost 的智能指针,比如 boost::scoped_ptr、boost::shared_array、boost::intrusive_ptr 也可以在编程实践中拿来使用,但这里不做进一步的介绍,有兴趣的读者可以参考:C++ 智能指针详解

在了解 STL 的四种智能指针后,大家可能会想另一个问题:在实际应用中,应使用哪种智能指针呢?

下面给出几个使用指南。

  1. 如果程序要使用多个指向同一个对象的指针,应选择 shared_ptr。这样的情况包括:
    (a)将指针作为参数或者函数的返回值进行传递的话,应该使用 shared_ptr;
    (b)两个对象都包含指向第三个对象的指针,此时应该使用 shared_ptr 来管理第三个对象;
    (c)STL 容器包含指针。很多 STL 算法都支持复制和赋值操作,这些操作可用于 shared_ptr,但不能用于 unique_ptr(编译器发出 warning)和 auto_ptr(行为不确定)。如果你的编译器没有提供 shared_ptr,可使用 Boost 库提供的 shared_ptr。

  2. 如果程序不需要多个指向同一个对象的指针,则可使用 unique_ptr。如果函数使用 new 分配内存,并返还指向该内存的指针,将其返回类型声明为 unique_ptr 是不错的选择。这样,所有权转让给接受返回值的 unique_ptr,而该智能指针将负责调用 delete。可将 unique_ptr 存储到 STL 容器中,只要对容器元素不使用拷贝操作的算法即可(如 sort())。例如,可在程序中使用类似于下面的代码段。

unique_ptr<int> make_int(int n)
{
    return unique_ptr<int>(new int(n));
}

void show(unique_ptr<int>& p1)
{
    cout << *p1 << ' ';
}

int main()
{
	//...
    vector<unique_ptr<int>> vp(size);
    for(int i = 0; i < vp.size(); i++)
    {
		vp[i] = make_int(rand() % 1000);       //copy temporary unique_ptr
	}
    vp.push_back(make_int(rand() % 1000));     //ok because arg is temporary
    for_each(vp.begin(), vp.end(), show);      //use for_each()
	//...
}

其中 push_back 调用没有问题,因为它返回一个临时 unique_ptr,该 unique_ptr 被赋给 vp 中的一个 unique_ptr。另外,如果按值而不是按引用给 show() 传递对象,for_each() 将非法,因为这将导致使用一个来自 vp 的非临时 unique_ptr 初始化 pi,而这是不允许的。前面说过,编译器将发现错误使用 unique_ptr 的企图。

在 unique_ptr 为右值时,可将其赋给 shared_ptr,这与将一个 unique_ptr 赋给另一个 unique_ptr 需要满足的条件相同,即 unique_ptr 必须是一个临时对象。与前面一样,在下面的代码中,make_int() 的返回类型为 unique_ptr:

unique_ptr<int> pup(make_int(rand() % 1000));		// ok
shared_ptr<int> spp(pup);										// not allowed, pup as lvalue
shared_ptr<int> spr(make_int(rand() % 1000));    	// ok

模板 shared_ptr 包含一个显式构造函数,可用于将右值 unique_ptr 转换为 shared_ptr。shared_ptr 将接管原来归 unique_ptr 所有的对象。

在满足 unique_ptr 要求的条件时,也可使用 auto_ptr,但 unique_ptr 是更好的选择。如果你的编译器没有unique_ptr,可考虑使用 Boost 库提供的 scoped_ptr,它与 unique_ptr 类似。

  1. 虽然说在满足 unique_ptr 要求的条件时,使用 auto_ptr 也可以完成对内存资源的管理,但是因为 auto_ ptr 不够安全,不提倡使用,即任何情况下都不应该使用 auto_ptr。

  2. 为了解决 shared_ptr 的循环引用问题,我们可以祭出 weak_ptr。

  3. 在局部作用域(例如函数内部或类内部),且不需要将指针作为参数或返回值进行传递的情况下,如果对性能要求严格,使用 scoped_ptr 的开销较 shared_ptr 会小一些。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++智能指针 的相关文章

  • c++11智能指针

    智能指针的原理 RAII RAII xff08 Resource Acquisition Is Initialization xff09 是一种利用对象生命周期来控制程序资源 xff08 如内存 文件句柄 网络连接 互斥量等等 xff09
  • C++智能指针

    文章目录 前言shared ptrunique ptrweak ptr使用智能指针安全吗 xff1f 前言 下面简单的概括了 3中智能指针的在使用时候需要注意的一些地方 xff0c 三种智能指针在离开其作用域之后都会被自动释放 xff0c
  • 【C++11】三大神器之——智能指针

    文章目录 前言 一 智能指针的原理1 RAII机制2 简单的实现 二 智能指针的用法1 智能指针的分类2 unique ptr基本语法 3 shared ptr基本语法 4 删除器5 weak ptr 前言 一 智能指针的原理 1 RAII
  • STL 智能指针

    转自 xff1a https blog csdn net k346k346 article details 81478223 STL一共给我们提供了四种智能指针 xff1a auto ptr unique ptr shared ptr和we
  • 面试可能会问六:智能指针

    先看一下百度百科的解释 xff1a 指针指针 当类中有指针成员时 xff0c 一般有两种方式来管理指针成员 xff1a 一是采用值型的方式管理 xff0c 每个类对象都保留一份指针指向的对象的拷贝 xff1b 另一种更优雅的方式是使用智能指
  • stl智能指针和boost智能指针对比

    先说说stl中的智能指针auto ptr xff0c 先说说auto ptr的特点 std auto ptr用来管理单个堆内存对象 xff0c 但是独享所有权 xff0c 且不允许赋值和拷贝 xff08 没有重载operator 61 xf
  • C++智能指针 shared_ptr,unique_ptr和weak_ptr

    1 智能指针为什么存在 因为C 没有自动回收内存的机制 因此每一次new出来的动态内存必须手动delete回去 因此智能指针可以解决这个问题 2 智能指针的大致描述是什么 智能指针 自动负责释放所指向的对象 实际上它利用了栈的机制 每一个智
  • 共享型智能指针的实现share_pointer

    智能指针是一个类 它产生的是一个类对象 而不是一个原生的指针对象 但是为了减少类对象与针对对象使用的差异性 所以share ptr类故意重载了两种常见的指针操作符 和 gt 从而share ptr与普通指针使用方式一样 简言之 就是shar
  • C++11之智能指针(unique_ptr、shared_ptr、weak_ptr、auto_ptr)浅谈内存管理

    目录 前言 智能指针 使用方法 unique ptr 实现unique ptr类 使用uniquePtr shared ptr 实现SharedPtr 使用shared ptr weak ptr 使用weak ptr 前言 下面这段代码看起
  • qt 智能指针介绍

    简介 Qt 提供了很多智能指针 比较常见的有 QPointer QSharedDataPointer QSharedPointer QWeakPointer 和 QScopedPointer 描述 QPointer 4 0 已经过时 可以被
  • 【C++11智能指针】unique_ptr概述、初始化、常用操作、返回unique_ptr、指定删除器、尺寸

    文章目录 1 unique ptr概述 2 unique ptr的初始化 2 1 直接初始化 2 2 make unique函数 3 unique ptr不支持拷贝构造和拷贝赋值 4 unique ptr支持移动构造和移动赋值 5 uniq
  • Qt 中的智能指针

    Qt 中的智能指针 上一篇博客中介绍了 C 11 标准中的提供的智能指针 在 Qt 中也提供了类似的替代功能 并且比 C 11 标准中提供的功能还要强大 所以如果我们使用 Qt 作为基础库 那么就没有必要使用C 11 的智能指针 Qt 的智
  • std:weak_ptr 用法小结。

    http blog csdn net coolmeme article details 43266319 参考了这篇博客 感谢博主的贡献 感谢博主的翻译 不过他写的太多了 我只是记录一下使用方法 原理就不深究了 需要了解其原理的可以自行去那
  • c++11 智能指针 (std::shared_ptr)(一)

    定义于头文件
  • 【C++11智能指针】weak_ptr概述、创建、常用操作、尺寸

    文章目录 1 weak ptr概述 2 weak ptr的创建 2 1 lock 3 weak ptr的常用操作 3 1 use count 3 2 expired 3 3 reset 4 weak ptr的尺寸 1 weak ptr概述
  • 智能指针之unique_ptr

    unique ptr实现的是专属所有权语义 用于独占它所指向的资源对象的场合 某个时刻只能有一个unique ptr指向一个动态分配的资源对象 也就是这个资源不会被多个unique ptr对象同时占有 它所管理的资源只能在unique pt
  • 内存的智能管理—智能指针

    前言 我们在使用C和C 进行开发的时候 申请堆区内存是必不可少的 但是很多时候 我们经常忘记释放他导致内存泄漏 从而导致程序崩溃 又或者在尚未使用完成的时候释放 从而导致出现野指针 都是非常危险的现象 所以我们在C 11的标准中提出了智能指
  • 【C++碎碎念】C++11新特性(声明、智能指针、右值引用、lambda表达式)

    目录 一 新类型 二 统一的初始化 三 声明 四 智能指针 五 右值引用 六 Lambda表达式 一 新类型 C 11新增了long long和unsigned long long 以支持64位 或更宽 的整型 新增了类型char16 t
  • C++智能指针详解

    1 概述 我们知道除了静态内存和栈内存外 每个程序还有一个内存池 这部分内存被称为自由空间或者堆 程序用堆来存储动态分配的对象即那些在程序运行时分配的对象 当动态对象不再使用时 我们的代码必须显式的销毁它们 在C 中 动态内存的管理是用一对
  • c++智能指针

    智能指针 智能指针也是模版 在头文件

随机推荐

  • C++头文件定义类的方法

    新手在写C 43 43 程序定义类的时候 xff0c 可能会犯一个错误 xff0c 就是在main函数文件里定义很多类 xff0c 一个文件中包含很多函数 xff0c 这样程序看起来很冗杂 今天总结一下如何在C 43 43 中使用头文件来定
  • 相机光学(十五)——如何提高相机标定的精度

    为了提高单目相机标定的精度 xff0c 认真看了张正友标定法的原文 xff0c 并且学习过网上一些牛人的方法 xff0c 但是大部分时候说的很笼统 xff0c 自己把这些经验总结起来并都测试了一下 xff0c 感觉靠谱的结论列出如下 xff
  • TCP/UDP、封装与解封装

    目录 传输类型 网络里面三层架构 TCP IP模型 OSI模型 TCP IP模型 掌握 TCP IP模型当中重点 数据传递过程中的封装和解封装 封装 解封装 TCP UDP ICMP ICMP错误报告 ICMP重定向 典型应用 PING应用
  • 解决 ERROR: cannot launch node of type [xxx]: can‘t locate node [xxx] in package [xxx]

    背景 xff1a 从github下载的ros代码 xff0c 修改添加节点后 xff0c catkin make 编译通过 xff0c 但在运行launch文件时候报错 原因 xff1a 1 从github上下载的很多文件 xff0c 下载
  • stm32控制步进电机

    本文使用DM542c驱动器驱动 使用前注意根据实际情况调节拨码开关 本文不会提到GPIO使能 xff0c 请自行使能 一 PWM操作驱动器使步进电机一直转 使能定时器时钟 xff0c 并配置基本参数 下图以TIM3为例 配置输出比较PWM1
  • 树莓派GPIO

    命令行执行下行 xff0c 即可得树莓派管脚编码表 gpio readall 也可看下图 xff1a BOARD 编号参考 Raspberry Pi 主板上 P1 接线柱的针脚编号 使用该方式的优点是无需考虑主板的修订版本 xff0c 无需
  • python opencv滤波

    1 均值滤波 算法简单 xff0c 计算速度快 xff0c 在去噪的同时去除了很多细节部分 xff0c 将图像变得模糊 cv2 blur 2 高斯滤波 去除高斯噪声 cv2 GaussianBlur 3 中值滤波 去除椒盐噪声 cv2 me
  • opencv imwrite()保存指定路径

    cpp为例 include lt opencv2 opencv hpp gt include lt string gt include lt iostream gt using namespace cv using namespace st
  • python pip安装的包的路径

    以ubuntu为例 从一个店家那里拿到的一个ubuntu环境中 xff0c 同时安装了python3 6和python2 7 xff0c 又安装了ros xff0c 最后pip安装包的位置很混乱 xff0c 安装的包不知道安装在了哪里 使用
  • solidworks实体显示线框

    sw有段时间没使用 xff0c 今天打开突然发现打开的sw窗口数超过1 xff0c 那么从第二个窗口以后的模型都显示成以下样子 xff08 无论是之前的文件还是新建的都不行 xff09 如上是一个圆盘 xff0c 明明是实体 xff0c 却
  • vscode使用虚拟环境

    我的conda没有添加入path xff0c 每次打开总是报错 一 选择对应虚拟环境的解释器 1 点击vscode的右下角这里 2 点击后可能会在vscode上方出现下图样子 xff0c 如果出现下图 xff0c 则点击第二项Select
  • TabError: inconsistent use of tabs and spaces in indentation

    错误原因是tab制表符和空格混用了 从其他地方复制源码容易出现此错误 解决办法 xff1a 把处于同级缩进的所有缩进修改统一 比较流行的几个编辑器都能标识tab和空格 xff0c 比如我用的vscode 用鼠标框选 不知道是tab还是空格的
  • 关于深度学习的问题笔记

    感谢沐神教我深度学习 x1f64f 损失为什么要平均 xff1f 平均即除以batch size xff0c 若不除 xff0c 则批越大梯度越大 xff0c 梯度下降的步长就越大 除以batch size可使梯度与批大小无关 也可以不在损
  • 简单(炫酷)的单链表快速排序写法

    昨天在复习快排的时候 在B站看到一个小哥哥说某大厂的面试让写一个单链表的快速排序 我们见的最多的快排写法都是从两端向中间扫描 这种写法在单链表上不能实现 哥们分析道 快排的核心思想是每次扫描后 所有pivot左侧的元素都比pivot小 右侧
  • char* char[]

    C 43 43 判断char 的指向 char a 61 34 Peter 34 char b 61 34 Peter 34 char c 61 new char 6 strcpy s c 6 34 Peter 34 这里a指向常量区 b指
  • HTTP 基本认证 HttpBasic

    HTTP 的认证机制 基本认证 摘要认证 一 基本认证 用BASE64 算法加密后的字符串放在HTTP Request中的Header Authorization中发送给服务端 xff0c 这种方式叫HTTP基本认证 Basic Authe
  • libcurl异步调用

    span class token keyword int span span class token function main span span class token punctuation span span class token
  • 标准c++库、stl库,boost库,qt库

    C 43 43 标准库 C C xff0b xff0b 标准库主要包含3部分 xff1a STL IO流及本地化 C的函数库 标准库不是STL STL是标准模板库 是标准库的一个子集 它是一个可复用的组件库 xff0c 其中包含了很多实用的
  • 生成2023年节假日/工作日维表

    项目中有一张维表 xff0c 维护的是历史节假日工作日的信息 xff0c 估计在很多场合都有类似的需求 到了新年 xff0c 需要生成新一年的数据 xff0c 下面看看如何在维表中插入新一年的数据 1 查询节假日 根据国务院发布的休假信息
  • C++智能指针

    参考 xff1a 头文件auto ptr使用示例 unique ptr类模板声明示例 shared ptr模板声明通过辅助类模拟实现 shared ptrshared ptr使用示例 weaked ptrweak ptr 用法weak pt