C++智能指针理解

智能指针和普通指针的区别

如果在程序中使用 new 从堆(自由存储区)分配内存,等到不需要时,应使用 delete 将其释放。 C++ 引用了智能指针 auto_ptr,以帮助自动完成这个过程。C++11摒弃了 auto_ptr,并新增了三种智能指针:unique_ptr、shared_ptr 和 weak_ptr。所有新增的智能指针都能与 STL 容器和移动语义协同工作。

智能指针和普通指针的区别

      智能指针和普通指针的区别在于智能指针实际上是对普通指针加了一层封装机制,区别是它负责自动释放所指的对象,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。

使用智能指针的原因

        申请的空间(即new出来的空间),在使用结束时,需要 delete 掉,否则会形成内存碎片。在程序运行期间,new 出来的对象,在析构函数中 delete 掉,但是这种方法不能解决所有问题,因为有时候 new 发生在某个全局函数里面,该方法会给程序员造成精神负担。此时,智能指针就派上了用场。使用智能指针可以很大程度上避免这个问题,因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源。所以,智能指针的作用原理就是在函数结束时自动释放内存空间,避免了手动释放内存空间。

智能指针的使用

(1)autoi_ptr(C++98 的方案,C++11已经弃用)
  auto_ptr 智能指针采用所有权模式;

auto_ptr<string> p1(new string("I reigned loney as a cloud.")); 
auto_ptr<string> p2; 
p2=p1; //auto_ptr不会报错

说明:此时不会报错,p2 剥夺了 p1 的所有权(相当于是两个指针同时指向一块内存),但是当程序运行时访问 p1 将会报错。所以auto_ptr 的缺点是:存在潜在的内存崩溃问题。

(2)unique_ptr(替换 auto_ptr)

unique_ptr<T>  智能指针对象名称(指针);

在上述格式中,unique_ptr<T> 是模板类型,后面是智能指针对象名称,遵守标识符命名规范。
智能指针对象名称后面的小括号中的参数是一个指针,该指针是 new 运算符申请堆内存空间返回的指针。
unique_ptr 实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对 象。它对于避免资源泄露,例如,以 new 创建对象后因为发生异常而忘记调用 delete 时的情形特别 有用。

  采用所有权模式,和上面例子一样。

#include <iostream>
 
using namespace std;
 
class A;
class B;
 
class B {
public:
	B() {
		cout << "执行B类的构造函数\n";
	}
	~B() {
		cout << "执行B类的析构函数" << endl;
	}
public:
	unique_ptr<A>a;
};
 
class A {
public:
	A() {
		cout << "执行A类的构造函数\n";
	}
	~A() {
		cout << "执行A类的析构函数" << endl;
	}
public:
	unique_ptr<B>b;
};
 
int main()
{
	unique_ptr<B>pb(new B);
	unique_ptr<A>pa(new A);
	//pb->a = pa; //此时会报错
 
	return 0;
}
unique_ptr<string> p3(new string("I reigned loney as a cloud.")); 
unique_ptr<string> p4; 
p4=p3; //此时会报错

编译器认为P4=P3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。 另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做(即源 unique_ptr 是临时右值时,源 unique_ptr 在赋值后不会出现被访问,所以编译器不会报错),比如:

unique_ptr<string> pu1(new string ("hello world")); 
unique_ptr<string> pu2; 
pu2 = pu1;                                    // #1 not allowed 
unique_ptr<string> pu3; 
pu3 = unique_ptr<string>(new string ("You")); // #2 allowed

说明:其中#1留下悬挂的 unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的 unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销 毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。

注意:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++ 有一个标准库函数 std::move(),让你能够将一个 unique_ptr 赋给另一个。例如:

unique_ptr<string> ps1, ps2; 
ps1 = demo("hello"); 
ps2 = move(ps1); 
ps1 = demo("alexia"); 
cout << *ps2 << *ps1 << endl;

(3)shared_ptr
       shared_ptr 实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计 数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除 了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用 release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

       shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。

shared_ptr 智能指针相关成员函数:

1)use_count:返回引用计数的个数;

2)unique:返回是否是独占所有权( use_count 为 1);

3)swap:交换两个 shared_ptr 对象(即交换所拥有的对象);

4)reset:放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少;

5)get:返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get() 是等价的;

(4)weak_ptr

相比于 unique_ptr 与 shared_ptr,weak_ptr 智能指针的使用更复杂一些,它可以指向 shared_ptr 管理的 new 对象,却没有该对象的所有权,即无法通过 weak_ptr 对象管理 new 对象。

图1   shared_ptr、weak_ptr 和 new 对象的关系示意图

在上图中,shared_ptr 对象和 weak_ptr 对象指向同一个 new 对象,但 weak_ptr 对象却不具有 new 对象的所有权。

weak_ptr 模板类没有提供与 unique_ptr、shared_ptr 相同的构造函数,因此,不能通过传递 new 对象指针的方式创建 weak_ptr 对象。weak_ptr 最常见的用法是验证 shared_ptr 对象的有效性。

weak_ptr 提供了一个成员函数 lock(),该函数用于返回一个 shared_ptr 对象,如果 weak_ptr 指向的 new 对象没有 shared_ptr 引用,则 lock() 函数返回 nullptr。

 weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象。,不能通过weak_ptr直接访问对象的方法,应该先把它转化为shared_ptr,如: shared_ptr p = pa->pb_.lock(),再去调用对象的成员函数,进行对象内存管理的是那个强引用的 shared_ptr。

weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增 加或减少。weak_ptr 是用来解决 shared_ptr 相互引用时的死锁问题,如果说两个 shared_ptr 相互 引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引 用,不会增加对象的引用计数,和 shared_ptr 之间可以相互转化,shared_ptr 可以直接赋值给 它,它可以通过调用 lock 函数来获得 shared_ptr。

class B; 
class A {
public:
    shared_ptr<B> pb_; 
    ~A() { 
        cout<<"A delete\n"; }
};
class B {
public: 
    shared_ptr<A> pa_; 
    ~B() { 
        cout<<"B delete\n"; }
};
void fun() { 
    shared_ptr<B> pb(new B()); 
    shared_ptr<A> pa(new A()); 
    pb->pa_ = pa; 
    pa->pb_ = pb; 
    cout<<pb.use_count()<<endl; 
    cout<<pa.use_count()<<endl; 
}
int main() { 
    fun(); 
    return 0; 
}

shared_ptr<B> pb(new B());函数退出时,B被析构,引用计数减一,但是 B 里面还引用了个 A ,此时计数为一,无 法释放资源,如果把两个 share_ptr 中一 个换成 weak_ptr 资源的引用计数为一, 析构后资源会被释放;



说明:  可以看到 fun 函数中 pa ,pb 之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针 pa,pb 析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有 被释放(AB的析构函数没有被调用),如果把其中一个改为 weak_ptr 就可以了,我们把类A里面 的 shared_ptr pb_; 改为 weak_ptr pb; 这样的话,资源B的引用开始就只有1,当  pb 析构时,B 的计数变为 0,B 得到释放,B 释放的同时也会使A的计数减一,同时 pa析构时使A的 计数减一,那么 A 的计数为 0,A 得到释放。

注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能 这样访问,pa->pb->print(); 英文pb是一个weak_ptr,应该先把它转化为shared_ptr,如: shared_ptr p = pa->pb_.lock(); p->print();

看一个修改后的例子:

#include <iostream>
 
using namespace std;
 
class A;
class B;
 
class B {
public:
	B() {
		cout << "执行B类的构造函数\n";
	}
	~B() {
		cout << "执行B类的析构函数,类A的引用计数为:" << a.use_count() << endl;
	}
public:
	shared_ptr<A>a;
};
 
class A {
public:
	A() {
		cout << "执行A类的构造函数\n";
	}
	~A() {
		cout << "执行A类的析构函数,类B的引用计数为:" << b.use_count() << endl;
	}
public:
	weak_ptr<B>b;
};
 
int main()
{
	shared_ptr<B>pb(new B);
	shared_ptr<A>pa(new A);
	pb->a = pa;
	pa->b = pb;
	//输出pb,pa的计数个数
	cout << "pb的计数:" << pb.use_count() << endl;
	cout << "pa的计数:" << pa.use_count() << endl;
 
	return 0;
}

打印结果:

总结:

(1)auto_ptr:管理权唯一,释放权唯一

存在的问题:当多个智能指针指向同一堆内存时,新智能指针具有管理权,原智能指针指向空,导致原智能指针失效

(2)unique_ptr:禁止拷贝和移动来保证所有权唯一

unique_ptr存在的问题:(1)不能数据共享;(2)在设计层面保证所有权唯一,但使用时并不能保证

(3)shared_ptr:带有引用计数

存在的问题:智能指针之间的相互引用问题会导致内存泄漏

(4)weak_ptr:不可单独使用,常与强智能指针结合使用,以解决强智能指针相互引用的问题


原文链接:

【C++】智能指针用法详解(非常实用)_c++智能指针用法_散修-小胖子的博客-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值