C++智能指针使用的那些事

C++智能指针使用的那些事

指针指针的由来

在C/C++里面,内存管理由开发者自己管理。指针变量总是指向一片内存空间,这片内存空间可以是局部变量、也可以是通过malloc、new申请的。如果申请的内存没有释放,就会导致内存泄漏。最终因为内存耗尽,服务被操作系统OOM掉

为了解决内存泄漏的问题,Java提出了虚拟机来管理内存,降低程序开发难度。C++则提出了智能指针,auto_ptr、shared_ptr、weak_ptr, unique_ptr,通过智能指针管理分配的内存单元,利用C++类对象的生命周期管理智能指针的生命周期结束后,会调用析构函数释放分配的内存。下面介绍这几个智能指针的使用方法。

auto_ptr 独占式指针

在构造的时候获取资源,在析构的时候释放资源,对指向符->、解引用进行重载,使用起来就像普通的指针。auto_ptr 会存在对象所有权转移的问题,在使用中需要谨慎注意。例如,下面这个case。

std::auto_ptr< string> ps(new string(“I reigned lonely as a cloud.”));
auto_ptr<string> vocation;
vocation = ps; // 所有权发生转移
cout << *ps << endl; // 程序崩溃
cout << *vocation << endl;

这段代码运行后,程序会直接crash。原因在于第三行已经把ps的所有权转移到 vocation,ps的值为0,对一个空指针取值就会出现crash。在使用auto_ptr的过程中,如果出现相互赋值,就很容易导致程序内存崩溃问题。在C++ 17里面,已经将auto_ptr 抛弃掉,引用了unique_ptr。

unique_ptr 独占式指针对象

在任何时间、资源只能被一个指针占有,当unique_ptr离开作用域,指针所包含的内容会被释放。这意味,unique_ptr无法赋值给其它变量,自然无法用在值传递的函数(存在赋值操作)。在C++ 11里面增加move语义,可以用于unique_ptr,也就是资源所有权的转移。下图演示了两个 unique_ptr 实例之间的所有权转换。

void test_pointer()

{

unique_ptr<CTest> ptr (new CTest());

unique_ptr<CTest> ptr2 = ptr; // 调用拷贝构造函数

ptr2 = ptr;

}

//运行结果

error: calling a private constructor of class ‘std::__1::unique_ptr<CTest,

std::__1::default_delete<CTest> >’

unique_ptr<CTest> ptr2 = ptr;

note: declared private here

unique_ptr(unique_ptr&);

note: declared private here

unique_ptr& operator=(unique_ptr&);

上述case,可以说明unique_ptr 无法通过拷贝构造函数创建对象,对象间无法赋值。从错误提示可以看出,unique_ptr的拷贝构造函数、赋值运算符重载函数都被定义为私有无法直接调用。

unique_ptr 直接在编译阶段规避auto_ptr相互赋值的隐患,这是unique_ptr的改进之处,还支持move语义。

shared_ptr 可共享对象的指针

上述的2个指针,只能独占对象,不支持共享,在使用中受到了极大的局限。为了能使共享变量也能用上智能指针,Boost率先实现了shared_ptr。它是一个共享所有权的智能指针,允许多个指针指向同一个对象。如今,已被引入到C11标准。下面是shared_ptr对象的结构示意图。

shared_ptr对象里面包含2个指针,指向对象T的指针Ptr,还有一个引用计数指针。引用计数指针指向了一个控制块,里面包括T类型对象的引用计数。每多一个智能指针指向对象时,引用数+1,而析构则相反,如果计数为零,则保存的指针被删除。

shared_ptr的基本用法


#include <memory>
void test_pointer()
{
// 两个指针指向同一个对象A
shared_ptr<CTest> ptr_shared(new CTest(“A”));
shared_ptr<CTest> ptr_shared2(ptr_shared);
cout << “reference value: ” << ptr_shared.use_count() << endl;
// 指向新对象B, A只有一个对象在引用
ptr_shared2.reset(new CTest(“B”));
cout << “reference value: ” << ptr_shared.use_count() << endl;

// 指向新对象C,对象A被析构,
ptr_shared.reset(new CTest(“C”));

{
// ptr_shared3 指向对象C
shared_ptr<CTest> ptr_shared3 = ptr_shared;
// ptr_shared 释放指向的对象C,此时对象C只有1个引用对象了
ptr_shared.reset();
cout << “reference value: ” << ptr_shared3.use_count() << endl;
}
// 出ptr_shared3 的作用域之后,对象C被释放。
}
//运行结果
CTest A
reference value: 2
CTest B
reference value: 1
CTest C
~CTest A
reference value: 1
~CTest C
~CTest B

多个shared_ptr类型指针可以指向同一个对象,shared_ptr的拷贝构造函数、赋值运算符重载为public,这有区别于unique_ptr。在日常开发中可以充分使用shared_ptr管理内存。

weak_ptr 间接引用对象的指针

weak_ptr没有重载operator* 和 operator->,因此它不具备普通指针的作用。它是shared_ptr的助手,用来获取对象的观测权。不可以直接引用对象,引用shared_ptr不会带来引用计数的变化,可以用来解决循环引用问题。主要成员函数如下。

  • use_count() 查看引用对象的引用计数。
  • expired() 查看引用对象是否存在,等价use_count(),但更快。
  • lock() 从引用对象的shared_ptr 获取一个shared_ptr 管理的对象,提升权力,可以操作对象。但要注意对象过期时,会返回一个存储空指针的shared_ptr。

weak_ptr基本用法

void test_weak_ptr()
{
// weak_ptr<CTest> ptr_weak(new CTest(“A”));// weak_ptr 不能直接引用对象
shared_ptr<CTest> ptr_shared(new CTest(“A”));
cout << “CTest value: ” << ptr_shared->get_value() << endl;
weak_ptr<CTest> ptr_weak(ptr_shared); // 仅能从shared_ptr 创建weak_ptr,引用计数没有发生变化
cout << “reference value: ” << ptr_shared.use_count() << “weak_ptr reference value:” << ptr_weak.use_count() << endl;

shared_ptr<CTest> ptr_shared2 = ptr_weak.lock(); // weak_ptr 提升出shared_ptr,可以操作对象
cout << “reference value: ” << ptr_shared.use_count() << “weak_ptr reference value:” << ptr_weak.use_count() << endl;
ptr_shared2->inc_value();
// 对象内容发生变化
cout << “CTest value: ” << ptr_shared->get_value() << endl;
}
// 运行结果
CTest A
CTest value: 0
reference value: 1weak_ptr reference value:1
reference value: 2weak_ptr reference value:2
CTest value: 1
~CTest A

借用一句金典的话,weak_ptr 就像是钢丝上的柳絮,只是用来观测shared_ptr引用对象是否过期,不可以直接操作对象。

总结

本篇文章简单介绍了C++ 中常用的智能指针auto_ptr、unique_ptr、shared_ptr、weak_ptr,并给出了具体用法。接下来会重点谈谈智能指针如何解决循环引用,如何实现一个智能指针,欢迎大家关注。

发表评论

邮箱地址不会被公开。 必填项已用*标注