stl中的智能指针类详解

 C++98/03的尝试——std::auto_ptr

 C++11标准废弃了std::auto_ptr(在C++17标准中被移除),取而代之的是std::unique_ptr,

std::auto_ptr容易让人误用的地方是其不常用的复制语义,即当复制一个std::auto_ptr对象时(拷贝复制或 operator=复制),原 std::auto_ptr 对象所持有的堆内存对象也会被转移给复制出来的新std::auto_ptr对象。

std::auto_ptr<int> sp1(new int(8));
std::auto_ptr<int> sp2(sp1);

std::auto_ptr<int> sp3(new int(8));
std::auto_ptr<int> sp4;
sp4 = sp3;

在以上代码中分别利用了拷贝构造(sp1=>sp2)和赋值构造(sp3=>sp4)来创建新的std::auto_ptr对象,因此sp1持有的堆对象被转移给sp2,sp3持有的堆对象被转移给sp4。

因为 std::auto_ptr 是不常用的复制语义,所以我们应该避免在 stl 容器中使用std::auto_ptr,当用算法对容器进行操作时(如最常见的容器元素遍历),很难避免不对容器中的元素进行赋值传递,这样便会使容器中的多个元素被置为空指针。

std::unique_ptr

std::unique_ptr 对其持有的堆内存具有唯一拥有权,也就是说该智能指针对资源(即其管理的堆内存)的引用计数永远是 1,std::unique_ptr 对象在销毁时会释放其持有的堆内存。

std::unique_ptr<int> sp1(new int(123));

std::unique_ptr<int> sp2;
sp2.reset(new int(123));

std::unique_ptr<int> sp3 = std::make_unique<int>(123);
//C++11实现make_unique
template<typename T,typename... Ts>
std::unique_ptr<T> make_unique(Ts&& ...params)
{
    return srd::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}

std::unique_ptr 禁止复制语义,为了达到这个效果,std::unique_ptr类的拷贝构造函数和赋值运算符(operator=)均被标记为=delete。

template<class T>
class unique_ptr
{
    //...
    unique_ptr(const unique_ptr&) = delete;
    unique_ptr& operator=(const unique_ptr&) = delete;
}



std::unique_ptr<int> sp1(std::make_unique<int>(123));
//一下代码无法通过编译
std::unique_ptr<int> sp2(sp1);

std::unique_ptr<int> sp3;
sp3 = sp1;

std::unique_ptr具有移动语义

std::unique_ptr<int> func(int val)
{
    std::unique_ptr<int> up(new int(val));
    return up;
}
int main()
{
    std::unique_ptr<int> sp1 = func(123);
    return 0;
}

以上代码从func函数中得到一个std::unique_ptr对象,然后返回给sp1。既然 std::unique_ptr 不能被复制,那么如何将一个 std::unique_ptr 对象持有的堆内存转移给另外一个呢?答案是使用移动构造。

并不是所有对象的std::move操作都有意义,只有实现了移动构造函数(Move Constructor)或移动赋值运算符(operator=)的类才行,而std::unique_ptr正好实现了二者。

我们可以通过给智能指针自定义资源回收函数来实现资源回收,自定义std::unique_ptr的资源释放函数的语法规则如下:

std::unique_ptr<T,DeletorFuncPtr>

其中,T 是我们要释放的对象类型,DeletorFuncPtr 是一个自定义函数指针。

class Socket
{
public:
    Socket()
    {
    }
    ~Socket()
    {
    }
    
    //关闭资源句柄
    void close()
    {
    }
};

int main()
{
    auto deletor = [](Socket* pSocket){
    
        //关闭句柄
        pSocket->close();
        delete pSocket;
    };
    
    std::unique_ptr<Socket,void(*)(Socket* pSocket)> spSocket(new Socket,deletor);
}

std::shared_ptr

std::shared_ptr持有的资源可以在多个std::shared_ptr之间共享,每多一个std::shared_ptr对资源的引用,资源引用计数就会增加1,在每一个指向该资源的std::shared_ptr对象析构时,资源引用计数都会减少1,最后一个 std::shared_ptr 对象析构时,若发现资源计数为 0,则将释放其持有的资源。多个线程之间递增和减少资源的引用计数都是安全的(注意:这不意味着多个线程同时操作std::shared_ptr管理的资源是安全的)。std::shared_ptr提供了一个use_count方法来获取当前管理的资源的引用计数。除了上面描述的内容,std::shared_ptr的用法和std::unique_ptr基本相同。

std::shared_ptr<int> sp1(new int(123));

std::shared_ptr<int> sp2;
sp2.reset(new int(123));

std::shared_ptr<int> sp3;
sp3 = std::make_shared<int>(123);

std::enable_shared_from_this

在实际开发中有时需要在类中返回包裹当前对象(this)的一个std::shared_ptr对象给外部使用,C++新标准也为我们考虑到了这一点,有如此需求的类只要继承自std::enable_shared_from_this<T>模板对象即可。

class A :public  std::enable_shared_from_this<A>
{
public:
	A()
	{
		std::cout << "A constructor" << std::endl;
	}
	~A()
	{
		std::cout << "A destructor" << std::endl;
	}

	std::shared_ptr<A> getSelf()
	{
		return shared_from_this();
	}
};

int main()
{
	std::shared_ptr<A> sp1(new A());
	std::shared_ptr<A> sp2 = sp1->getSelf();

	std::cout << "use count: " << sp1.use_count() << std::endl;
	return 0;
}

在以上代码中,类A继承自std::enable_shared_from_this<A>并提供了一个getSelf方法返回自身的std::shared_ptr对象,在getSelf方法中调用了shared_from_this方法。

注意事项一:不应该共享栈对象的this指针给智能指针对象。(智能指针管理的是堆对象,栈对象会在函数调用结束后自行销毁,因此不能通过shared_from_this()将该对象交由智能指针对象管理)

注意事项二:std::enable_shared_from_this的循环引用问题。(一个资源的生命周期可以交给一个智能指针对象来管理,但是该智能指针的生命周期不可以再交给该资源来管理。)

std::weak_ptr

std::weak_ptr是一个不控制资源生命周期的智能指针,是对对象的一种弱引用,只提供了对其管理的资源的一个访问手段,引入它的目的是协助std::shared_ptr工作。

std::weak_ptr 可以从一个 std::shared_ptr 或另一个 std::weak_ptr 对象构造,std::shared_ptr可以直接赋值给std::weak_ptr,也可以通过std::weak_ptr的lock函数来获得std::shared_ptr。它的构造和析构不会引起引用计数的增加或减少。std::weak_ptr 可用来解决 std::shared_ptr 相互引用时的死锁问题,即两个 std::shared_ptr 相互引用,这两个智能指针的资源引用计数就永远不可能减少为0,资源永远不会被释放。

#include<iostream>
#include<memory>

int mian()
{
	std::shared_ptr<int> sp1(new int(123));
	std::cout << "use count: " << sp1.use_count() << std::endl;   //use count: 1

	std::weak_ptr<int> sp2(sp1);
	std::cout << "use count: " << sp1.use_count() << std::endl;   //use count: 1

	std::weak_ptr<int> sp3 = sp1;
	std::cout << "use count: " << sp1.use_count() << std::endl;   //use count: 1

	std::weak_ptr<int> sp4 = sp2;
	std::cout << "use count: " << sp1.use_count() << std::endl;   //use count: 1

	return 0;
}

无论通过何种方式创建std::weak_ptr,都不会增加资源的引用计数,因此每次输出的sp1的引用计数值都是1。

weak_ptr资源是否有效以及获取管理对象

//m tmpConn 是一个 std::weak ptr<TcpConnection>对象
//m tmpConn 引用的 TcpConnection 已被销毁,直接返回
if(m tmpConn.expired())
    return;
std::shared ptr<TcpConnection>conn = m tmpConn.lock();
if (conn)
{
    //省略对 conn的后续操作代码
}

注意:std::weak_ptr 类没有重写 operator->和operator*方法,因此不能像std::shared_ptr 或 std::unique_ptr 一样直接操作对象,std::weak_ptr 类也没有重写 operator bool()操作,因此不能通过std::weak_ptr对象直接判断其引用的资源是否存在。

std::weak_ptr应用场景中的经典例子是订阅者模式或者观察者模式。这里以订阅者为例来说明,消息发布器只有在某个订阅者存在的情况下才会向其发布消息,不能管理订阅者的生命周期。

class Subscriber
{
	//具体实现....
};
class SubscribeManager
{
public:
	void publish()
	{
		for (const auto& iter : m_subscribers)
		{
			if (!iter.expired())
			{

			}
		}
	}
private:
	std::vector<std::weak_ptr<Subscriber>> m_subscribers;
};

智能指针对象的大小

一个std::unique_ptr对象的大小与裸指针的大小相同(即sizeof(std::unique_ptr<T>)==sizeof(void*)),而 std::shared_ptr 的大小是std::unique_ptr 的两倍。

使用智能指针时的注意事项

1.一旦使用了智能指针管理一个对象,就不该再使用原始裸指针去操作它

2.知道在哪些场合使用哪种类型的智能指针

        在通常情况下,如果我们的资源不需要在其他地方共享,就应该优先使用std::unique_ptr,反之使用 std::shared_ptr。当然,这是在该智能指针需要管理资源的生命周期的情况下进行的;如果不需要管理对象的生命周期,则请使用std::weak_ptr。

3.认真考虑,避免操作某个引用资源已经释放的智能指针

4.作为类成员变量,应该优先使用前置声明(forward declarations)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值