C++11 智能指针 (整理)

RAII技术

RAII技术即资源分配及初始化,使用类来封装资源的分配和初始化,再构造函数完成资源的分配和初始化,析构函数完成资源的清理,可以保证正确的初始化和资源释放



智能指针


什么是智能指针?


智能指针是借用RAII技术对普通指针进行封装,实质是一个对象,表现为一个指针,即智能的管理动态资源的释放。


智能指针的作用


1.防止忘记调用delete操作符释放资源,与程序异常后进入catch忘记释放内存及多次释放同一块内存引起的内存泄漏
2.将值语义转为引用语义



智能指针的使用

本文介绍三个智能指针:unique_ptr、shared_ptr、weak_ptr。auto_ptr已经被C++11丢弃了。



shared_ptr


1.shared_ptr多个指针指向相同的对象,使用引用计数,每一次shared_ptr的拷贝指向相同的内存,每使用一次,内部引用计数加1,每析构一次,内部引用计数减1,直至减为0,自动删除指向的堆内存,shared_ptr内部的引用计数是线程安全的,但对象的读取需要加锁
2.智能指针是一个模板类,可以指定其类型,传入的指针使用构造函数初始化,也可使用make_shared函数初始化,不能把一个指针赋给智能指针,因为一个是类,一个是指针,如shared_ptr p=new int(1);错误
3.拷贝使得对象的引用计数加1,赋值使得原对象的引用计数减1,当计数为0时,自动删除指向的内存,后来指向的对象的引用计数加1,指向后来的对象。
4.缺陷为:循环引用,会导致内存泄漏问题,解决办法为使用弱引用的智能指针,即weak_ptr

1、shared_ptr是只有c++11版本才能使用的。因此再编译或者再编写makefile的时候要注意一下。

2、shared_ptr unique_ptr 两者最大的不同在于它支持两个指针指向同一块内存。

3、shared_ptr支持delete []。c++17前std::shared_ptr都有一个严重的限制,那就是它并不支持动态数组,这个要注意;

#include <iostream>
#include <memory>

int main() {
    {
        int a = 10;
        std::shared_ptr<int> ptra = std::make_shared<int>(a);
        std::shared_ptr<int> ptra2(ptra); //copy
        std::cout << ptra.use_count() << std::endl;   //= 2

        int b = 20;
        int *pb = &a;
        //std::shared_ptr<int> ptrb = pb;  //error
        std::shared_ptr<int> ptrb = std::make_shared<int>(b);
        ptra2 = ptrb; //assign           -1
        pb = ptrb.get(); //获取原始指针

        std::cout << ptra.use_count() << std::endl;  //= 1
        std::cout << ptrb.use_count() << std::endl;   //= 2
    }
}


unique_ptr


1.unique_ptr唯一拥有其所指对象,在同一时刻只能有一个unique_ptr指向给定对象(不允许拷贝,赋值操作),与原始指针不同的是,当异常发生时,动态资源得以释放。
2.unique_ptr本身的生命周期:从unique_ptr指针创建到离开作用域,离开作用域时,若其指向对象,所指对象被销毁(默认使用delete操作符)
3.unique_ptr指针与所指向对象的关系:创建智能指针使用构造函数初始化,使用reset重新指定,使用release释放所有权,通过移动语义转移所有权。
 

这里稍微说以下如何用unique_ptr进行相互赋值,这当中有三种情况,第一种情况是两个指针都是unique_ptr,第二个情况是我要把unique_ptr变成普通指针,第三种情况是我要把普通指针变成unique_ptr。我们举个例子 

  unique_ptr <AutoPtr> autoPtrFirst(new AutoPtr(1));

       unique_ptr <AutoPtr> autoPtrSecond(new AutoPtr(2));

  AutoPtr *p = NULL;

  好,我们针对以上三种情况进行讨论。

  第一种情况:假如我们要把autoPtrSecond指向autoPtrFirst那么我们应该怎么做呢?我们可以这样写。autoPtrSecond = std::move(autoPtrFirst),即把First指针移动走,只留下Second指针。
  第二种情况:如果我要把unique_ptr指针变成普通指针应该怎么做呢?我们可以这样写。p = autoPtrFirst.release(),这样我们就可以把autoPtrFirst移动走,然后让p指向原来的地址空间了。

  第三种情况:如果我们要把普通指针变成unique_ptr指针应该怎么做呢?我们可以这样写。autoPtrFirst.reset(p),这样我们就可以将autoPtrFirst指向p了。

支持动态数组:

std::unique_ptr<int[]> up1(new int[10]());

 建议:使用 vector<T> 容器而不是 unique_ptr<T[]>,因为该容器类型比智能指针强大得多,也灵活得多。

#include <iostream>
#include <memory>

int main() {
    {
        std::unique_ptr<int> uptr(new int(10));  //绑定动态对象
        //std::unique_ptr<int> uptr2 = uptr;  //不能賦值
        //std::unique_ptr<int> uptr2(uptr);  //不能拷贝
        std::unique_ptr<int> uptr2 = std::move(uptr); //转换所有权
        uptr2.release(); //释放所有权
    }
    //超过uptr的作用域,內存释放
}



weak_ptr


weak_ptr是配合shared_ptr出现的,不具有普通指针的行为,没有重载operator*和->最大作用在于协助shared_ptr观测资源的使用情况,可以从一个shared_ptr或一个weak_ptr对象构造,使用其成员函数use_count()获得观测资源的引用计数,expired()功能等价于use_count()==0标识被观测的资源已经不存在。
weak_ptr可以使用lock()获得一个新的shared_ptr对象,从而操作资源,但expired()==true时,lock函数返回一个存储空指针的shared_ptr对象

#include <iostream>
#include <memory>

int main() {
    {
        std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
        std::cout << sh_ptr.use_count() << std::endl;

        std::weak_ptr<int> wp(sh_ptr);
        std::cout << wp.use_count() << std::endl;

        if(!wp.expired()){
            std::shared_ptr<int> sh_ptr2 = wp.lock(); //get another shared_ptr
            *sh_ptr = 100;
            std::cout << wp.use_count() << std::endl;
        }
    }
    //delete memory
}

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变 shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放,因此,weak_ptr的名字抓住了这种智能指针“弱”共享对象的特点。

weak_ptr的出现是为了解决shared_ptr循环引用的。

class B;
class A {
public:
	A() { std::cout << "A's constructor " << std::endl; }
	~A() { std::cout << "A's disconstructor " << std::endl; }
	std::shared_ptr<B> b;
};
 
class B {
public:
	B() { std::cout << "B's constructor " << std::endl; }
	~B() { std::cout << "B's disconstructor " << std::endl; }
	std::shared_ptr<A> a;
};
 
 
 
int main()
{
	std::shared_ptr<A> spa = std::make_shared<A>();
	std::shared_ptr<B> spb = std::make_shared<B>();
	spa->b = spb;
	spb->a = spa;
	std::cout << "sqa的强引用个数 " << spa.use_count() << std::endl;
	std::cout << "sqb的强引用个数 " << spb.use_count() << std::endl;
 
	return 0;
}
 
 
// 输出结果
A's constructor
B's constructor
sqa的强引用个数 2
sqb的强引用个数 2

从上可以看出,由于spa和spb的强引用计数永远大于等于1,所以A和B的析构函数不会被调用。

当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它

	auto p = make_shared<int>(10);
	weak_ptr<int> wp(p);
	std::cout << "p的强引用为 " << p.use_count() << std::endl;    // 为1

wp和p指向相同的对象,由于是弱引用,创建wp不会改变p的引用计数;wp指向的对象可能被释放掉。

利用weak_ptr修改上面的循环引用。

class B;
class A {
public:
	A() { std::cout << "A's constructor " << std::endl; }
	~A() { std::cout << "A's disconstructor " << std::endl; }
	std::shared_ptr<B> b;
};
 
class B {
public:
	B() { std::cout << "B's constructor " << std::endl; }
	~B() { std::cout << "B's disconstructor " << std::endl; }
	std::weak_ptr<A> a;
};
 
 
 
int main()
{
	std::shared_ptr<A> spa = std::make_shared<A>();
	std::shared_ptr<B> spb = std::make_shared<B>();
	spa->b = spb;
	spb->a = spa;
	std::cout << "sqa的强引用个数 " << spa.use_count() << std::endl;
	std::cout << "sqb的强引用个数 " << spb.use_count() << std::endl;
 
	return 0;
}
 
 
 
// 结果输出
A's constructor
B's constructor
sqa的强引用个数 1
sqb的强引用个数 2
A's disconstructor
B's disconstructor

可以看出spa的强引用变为1,然后其释放后 spb 为1 。所以weak_ptr配合shared_ptr解决循环引用问题。

weak_ptr使用注意点

因为weak_ptr是一种弱引用,其指向shared_ptr,可能shared_ptr已经释放了。所以我们不能使用shared_ptr直接访问对象。先看下weak_ptr的用法:

weak_ptr<T> w //空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp) //与shared_ptr sp指向相同对象的weak_ptr。T必须能转换为sp指向的S型
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,否贝y返回 false
w.lock() //如果expired为true,返回一个空shared ptr:否则返回一个 指向w的对象的shared_ptr

所以我们用weak访问对象时,可以通过lock来判断

	auto p = make_shared<int>(10);
	weak_ptr<int> wp(p);
	if (std::shared_ptr<int> np = wp.lock()) {   // 如果np不为空则条件成立
		// np和p共享对象
	}

智能指针的实现

智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。

#include <iostream>
#include <memory>

template<typename T>
class SmartPointer {
private:
    T* _ptr;
    size_t* _count;
public:
    SmartPointer(T* ptr = nullptr) :
            _ptr(ptr) {
        if (_ptr) {
            _count = new size_t(1);
        } else {
            _count = new size_t(0);
        }
    }

    SmartPointer(const SmartPointer& ptr) {
        if (this != &ptr) {
            this->_ptr = ptr._ptr;
            this->_count = ptr._count;
            (*this->_count)++;
        }
    }

    SmartPointer& operator=(const SmartPointer& ptr) {
        if (this->_ptr == ptr._ptr) {
            return *this;
        }

        if (this->_ptr) {
            (*this->_count)--;
            if (this->_count == 0) {
                delete this->_ptr;
                delete this->_count;
            }
        }

        this->_ptr = ptr._ptr;
        this->_count = ptr._count;
        (*this->_count)++;
        return *this;
    }

    T& operator*() {
        assert(this->_ptr == nullptr);
        return *(this->_ptr);

    }

    T* operator->() {
        assert(this->_ptr == nullptr);
        return this->_ptr;
    }

    ~SmartPointer() {
        (*this->_count)--;
        if (*this->_count == 0) {
            delete this->_ptr;
            delete this->_count;
        }
    }

    size_t use_count(){
        return *this->_count;
    }
};

int main() {
    {
        SmartPointer<int> sp(new int(10));
        SmartPointer<int> sp2(sp);
        SmartPointer<int> sp3(new int(20));
        sp2 = sp3;
        std::cout << sp.use_count() << std::endl;
        std::cout << sp3.use_count() << std::endl;
    }
    //delete operator
}

6 shared_ptr多线程

shared_ptr的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化。这部分请看陈硕大佬的博客:为什么多线程读写 shared_ptr 要加锁?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恋恋西风

up up up

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值