[C++11] 智能指针

智能指针原理

        由于C++中没有垃圾清理机制,分配的内存需手动释放,否则就会产生内存泄漏,C++11中引入了智能指针的概念,使用其可以有效解决该问题。智能指针是存储指向动态分配对象指针的类。使用引用计数的技术,使用一次内部引用计数 +1,每析构一次,内部引用计数 -1,减为0时,删除所指向的堆内存。

        使用智能指针可以确保在离开指针作用域时,自动销毁动态分配的对象,防止内存泄漏。C++11中提供了三种智能指针,包含在<memory>中:

        1. 共享的智能指针:std::shared_ptr

        2. 独占的智能指针:std::unique_ptr

        3. 弱引用智能指针:std::weak_ptr

        

std::unique_ptr

        unique_ptr是作用域指针,指超出作用域就会销毁并调用delete。unique_ptr是唯一的,不可拷贝,不可共享。

使用C++14中引入的std::make_unique<>()函数来创建,可以保证内存安全。

std::unique_ptr<student> uptr = std::make_unique<student>();

#include <iostream>
#include <memory>


class student{
public:
    student(){
        std::cout << "constructor" << std::endl;
    }

    ~student(){
        std::cout << "destructor" << std::endl;
    }
     void print(){std::cout<<"print"<<std::endl;}
};

   int main(){

    {
        
        std::unique_ptr<student> stu = new student(); // error! unique_ptr不能隐式转换
        std::unique_ptr<student> stu(new student());//ok,可以但不建议

        std::unique_ptr<student> uptr = std::make_unique<student>(); //推荐使用
        uptr->print();  //像一般原始指针使用

    }
return 0;
   }

std::shared_ptr

        shared_ptr的工作方式是采用引用计数。

shared_ptr需要分配另一块内存,叫控制块,来存储引用计数。比如说new一个student类的实例,传递给shared_ptr的构造函数,那么shared_ptr要做两次分配:一次new student的分配,一次控制块的分配。可以使用make_shared提高效率,保证安全。

std::shared_ptr<student> sptr = std::make_shared<student>();

代码举例:

#include<iostream>
#include<memory>

using namespace std;
class student{
public:    
    student(){ cout<<"constructor"<<endl; }

    ~student(){ cout<<"destructor"<<endl; }

};

int main(){
    {
        shared_ptr<student> sp;
        {
            //    shared_ptr<student> sptr = sptr(new student());    不推荐这种

            shared_ptr<student> sptr = make_shared<student>();
            sp = sptr;    //可以拷贝
        }    //此时离开了作用域,sptr已经销毁了,但是没有调用析构,因为sp还活着,持有着该student的引用             
            //计数由2 -> 1

    }    //这时所有引用消失,计数变为0,调用析构,释放内存。
return 0;
}

shared_ptr提供了几个函数:

初始化:

        void reset() noexcept;


        template< typename T >

        void reset( T* ptr );

        template< typename T, typename Deleter >

        void reset( T* ptr , Deleter d );
 

        ptr:指向要获得所有权的对象的指针

        d:指定删除器的指针

获取原始指针:
        T* get() const noexcept;
 

统计指向同一内存对象的智能指针的个数:

        long use_count() const noexcept;

交换两个智能指针所指向的内存地址:

        void swap(shared_ptr &r) noexcept;

 shared_ptr的循环引用

        当两个对象(主体是对象)使用shared_ptr相互引用时,那么当超出范围时,都不会删除内存。发生这种情况的原因是shared_ptr在其析构函数中递减关联内存的引用计数后,检查count是否为0,如果不为0,析构函数就不会释放相应的内存。当出现了循环引用后,就会发现count的值总是不为0。

        为了解决循环引用问题,采用weak_ptr弱引用可以避免产生此问题。weak_ptr不会增加引用计数,如果对象活着,那么它可提升为有效的shared_ptr,如果对象已经死了,提升失败返回空的shared_ptr。提升/lock()行为是线程安全的。

std::weak_ptr

        可以和shared_ptr一起使用。weak_ptr可以被拷贝,但是不会增加额外的控制块来控制计数,仅表示该指针存活。

将一个shared_ptr赋值给另一个shared_ptr,引用计数加一;将一个shared_ptr赋值给一个weak_ptr时,不增加引用计数。 weak_ptr不控制对象的生命期,但是知道对象是否活着。

{
    std::weak_ptr<student> stu;
    {
        std::shared_ptr<student> sptr = std::make_shared<student>();
        stu = sptr;
    } //此时,此析构被调用,内存被释放
}

weak_ptr的应用场景:

弱智能指针 weak_ptr 与 shared_ptr 的区别在于:

1. weak_ptr不会改变资源的引用计数,只是一个观察者的角色,通过观察shared_ptr来判定资源是否存在。
2. weak_ptr持有的引用计数,不是资源的引用计数,而是同一个资源的观察者的计数。
3. weak_ptr没有提供常用的指针操作,无法直接访问资源,需要先通过lock()提升为shared_ptr强智能指针,才能访问资源。

 根据以上特性,weak_ptr可以用来在以下场景得到应用:

        1. observer模式

        2. 解决循环引用

        3. 弱回调

shared_ptr的线程安全

        shared_ptr的引用计数本身是安全且无锁的,但是对象的读写并不是,因为shared_ptr有两个数据成员,读写操作不能原子化。

        shared_ptr的线程安全级别:

        1. 一个shard_ptr对象实体可被多个线程同时读取;

        2. 两个shard_ptr对象实体可以被两个线程同时写入,析构算写操作;

        3. 如果要从多个线程中读取同一个shard_ptr对象,需要加锁。

在多个线程中同时访问同一个shard_ptr,使用mutex保护:

class Foo{
    //......
};

mutex mt;
shared_ptr<Foo> ptr;

void doit(const shared_ptr<Foo>& pFoo) {
    //......
}
void read() {
	shared_ptr<Foo> localptr;
	{
		lock_guard<mutex> lock(mt);
		localptr = ptr;
    }
		doit(localptr);
}
void write() {
	shared_ptr<Foo> newptr = make_shared<Foo>();
	{
		lock_guard<mutex> lock(mt);
		ptr = newptr;
    }
		doit(newptr);
}

         在上面的read()和write()函数中,临界区外都没访问ptr,而是采用一个指向同一Foo对象的栈上的拷贝。使用这种local copy,shared_ptr 在作为函数参数传递时不必复制,用reference to const 作为参数类型即可。

        如果想要销毁对象,可以在临界区内执行 ptr.reset(),但是这样会增长临界区长度。一种改进方法就是像上面那样定义一个localptr,让它在临界区内与ptr交换,这样能保证把对象的销毁推迟到临界区外。

void write() {
	shared_ptr<Foo> localptr = make_shared<Foo>();
	{
		lock_guard<mutex> lock(mt);
	    localptr.swap(ptr);
    }
        localptr.reset();
}

        引用当年孟岩老师说过的话:“C++ 利用智能指针达成的效果是:一旦某对象不再被引用,系统刻不容缓,立刻回收内存。这通常发生在关键任务完成后的清理(clean up)时期,不会影响关键任务的实时性,同时,内存里所有的对象都是有用的,绝对没有垃圾空占内存。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值