12.11 C++11新增的这个智能指针特别霸道,只允许自己访问相关内存

本文将结合C++11标准中的智能指针std::unique_ptr<>类的简单使用实例,讨论其基本原理,以期快速了解该智能指针类的使用。

C++11新增的这个智能指针特别霸道,只允许自己访问相关内存

std::unique_ptr<> 是什么?

std::unique_ptr<> 是什么?

std::unique_ptr<>是C++语言中提供的一种智能指针类,使用它可以方便的管理指针,尽可能的避免内存泄漏。unique_ptr 对象可以用于维护普通(常常用于索引一块内存)的指针,在其生命周期结束后,自动地删除它,并释放相关的内存。unique_ptr 重载了->和*运算符,因此可以像使用普通指针那样使用 unique_ptr 智能指针。下面是一段简单的C++语言代码示例,请看:

#include <iostream>
#include <memory>

struct Task
{
int mId;
Task(int id ) :mId(id)
{
std::cout< }
~Task()
{
std::cout< }
};

int main()
{
// 创建一个 unique_ptr 对象,并且绑定到 new Task(23) 上
std::unique_ptr<task> taskPtr(new Task(23));
// 通过智能指针访问成员变量
int id = taskPtr->mId;
std::cout<
return 0;
}
/<task>/<memory>/<iostream>
C++11新增的这个智能指针特别霸道,只允许自己访问相关内存

C++代码示例

这段C++语言代码很简单,main() 函数首先创建了一个 unique_ptr 智能指针对象,new Task(23)本来需要一个Task *指针索引,现在直接使用 unique_ptr 对象 taskPtr 管理它,所以代码中没有使用delete删除相应的指针,因为“智能”指针对象 taskPtr 会在相关指针生命周期结束后自动地删除它。编译这段C++语言代码时,记得指定-std=c++11选项,最终得到的输出如下:

# g++ t1.cpp -std=c++11
# ./a.out
Task::Constructor
23
Task::Destructor

事实上,不管函数是正常还是不正常(程序抛出异常等)的退出,taskPtr 的析构函数总是会被调用的,因此,taskPtr 管理的 raw 指针会被自动删除,避免内存泄漏。

unique_ptr 的“专享所有权”

unique_ptr 中的“unique”一词有着“唯一的,独一无二”的意思,这主要体现在所有权上,某个 raw 指针同时只能被一个 unique_ptr 指针绑定,我们不能拷贝 unique_ptr 对象,只能转移。事实上,鉴于 unique_ptr 对象相对于其管理的 raw 指针的独一无二特性,其内部不需要像shared_ptr<>智能指针类那样需要“引用计数”机制,一旦 unique_ptr 对象的析构函数被调用,它就会删除掉绑定的 raw 指针。

C++11新增的这个智能指针特别霸道,只允许自己访问相关内存

使用 unique_ptr<> 智能指针类

创建一个空 unique_ptr 对象

std::unique_ptr<> 本质上是一个在标准命名空间std中的模板类,使用它需要包含头文件<memory>,例如定义一个可以绑定 int 指针的对象的C++语言代码可以如下写:/<memory>

#include <memory>

std::unique_ptr ptr;/<memory>

还可以在定义 unique_ptr 对象的时候传入需要与之绑定的“raw指针”,这一点在前面的C++语言代码实例中已经见过:

std::unique_ptr<task> taskPtr(new Task(23));/<task>

需要注意,不能直接把 raw 指针直接赋值给 unique_ptr 对象,也就是说下面这行C++语言代码是非法的:

taskPtr = new Task(23); // 非法

检查 unique_ptr 对象是否为空

上面定义的 unique_ptr 对象 ptr 没有与任何 raw 指针绑定,因此它是空的。检查 unique_ptr 对象是否为空,一般有两种方法,相关的C++语言代码示例如下,请看:

  • 方法 1:
if(!ptr)
std::cout<
  • 方法 2:
if(ptr == nullptr)
std::cout<

重置(reset)unique_ptr

调用 unique_ptr 的 reset() 方法将删除与之绑定的 raw 指针,并且将 unique_ptr 对象置空:

taskPtr.reset();
// taskPtr==nullptr 为真

上面这行C++语言代码执行后,与 taskPtr 绑定的 raw 指针将会被删除,并且 taskPtr 被置空,也即taskPtr==nullptr为真。

unique_ptr 对象不可拷贝

鉴于 unique_ptr 不可拷贝,只能移动,所以我们不能通过拷贝构造函数活着赋值操作拷贝 unique_ptr 对象,下面这两行C++语言代码都是非法的:

std::unique_ptr<task> taskPtr2 = taskPtr; // 非法
taskPtr = taskPtr2; // 非法/<task>

转移 unique_ptr 对象的所有权

如前文所述,我们不能拷贝 unique_ptr 对象,却可以移动它,所谓“移动”,其实就是转移所有权,请看下面这个示例,首先创建一个 unique_ptr 对象:

std::unique_ptr<task> taskPtr2(new Task(55));/<task>

此时 taskPtr2 显然不为空。现在将其对绑定 raw 指针的所有权转移到一个新的 unique_ptr 对象,相关的C++语言代码可以如下写:

std::unique_ptr<task> taskPtr4 = std::move(taskPtr2);

if(taskPtr2 == nullptr)
std::cout<
// taskPtr2 对 raw 指针的所有权被转移给 taskPtr4 了
if(taskPtr4 != nullptr)
std::cout<

std::move() 函数将 taskPtr2 转换为右值(rvalue)引用,所以 unique_ptr 的移动构造函数可以将与 taskPtr2 绑定的 raw 指针转移给 taskPtr4,在这之后,taskPtr2 变为空。

释放对绑定 raw 指针的所有权

unique_ptr 的 release() 函数可以直接将对绑定 raw 指针的所有权释放,该函数会将绑定的 raw 指针返回,请看下面的C++语言代码示例:

std::unique_ptr<task> taskPtr5(new Task(55));

if(taskPtr5 != nullptr)
std::cout<
// 释放所有权
Task * ptr = taskPtr5.release();

if(taskPtr5 == nullptr)
std::cout<

执行完上面的代码后,taskPtr5 变为空,并且其绑定的 raw 指针被赋值给 ptr。

完整示例

下面以一段完整的C++语言代码示例结束本文:

#include <iostream>
#include <memory>

struct Task
{
int mId;
Task(int id ) :mId(id)
{
std::cout< }
~Task()
{
std::cout< }
};

int main()
{
std::unique_ptr ptr1;
if(!ptr1)

std::cout< if(ptr1 == nullptr)
std::cout<
// 不能直接将 raw 指针赋值给 unique_ptr 对象
// std::unique_ptr<task> taskPtr2 = new Task(); // 非法

std::unique_ptr<task> taskPtr(new Task(23));
if(taskPtr != nullptr)
std::cout< // 通过 unique_ptr 直接访问成员变量
std::cout<< taskPtr->mId <<:endl>
taskPtr.reset();
std::cout< if(taskPtr == nullptr)
std::cout<
std::unique_ptr<task> taskPtr2(new Task(55));
// unique_ptr 不可赋值,不可拷贝
//taskPtr = taskPtr2; // 非法
//std::unique_ptr<task> taskPtr3 = taskPtr2; // 非法

{
// 转移所有权
std::unique_ptr<task> taskPtr4 = std::move(taskPtr2);
if(taskPtr2 == nullptr)
std::cout< if(taskPtr4 != nullptr)
std::cout< // taskPtr4 作用域尾部,生命周期结束,
// 将删除 taskPtr2 转移过来的 raw 指针
}
std::unique_ptr<task> taskPtr5(new Task(55));
// 释放所有权
Task * ptr = taskPtr5.release();
if(taskPtr5 == nullptr)
std::cout< std::cout<<ptr->mId<<:endl> // 此时需要手动删除用完的指针
delete ptr;

return 0;
}/<ptr->/<task>/<task>/<task>/<task>/<task>/<task>
/<memory>/<iostream>
C++11新增的这个智能指针特别霸道,只允许自己访问相关内存

代码看着不方便的话,可以点击文章末尾的“了解更多”

同样的,编译时需要指定-std=c++11,最终输出如下,请看:

# g++ t2.cpp -std=c++11
# ./a.out
ptr1 is empty
ptr1 is empty
Task::Constructor
taskPtr is not empty
23
Task::Destructor
Reset the taskPtr
taskPtr is empty
Task::Constructor
taskPtr2 is empty
taskPtr4 is not empty
Task::Destructor
Task::Constructor
taskPtr5 is empty
55
Task::Destructor

小结

本文主要讨论了C++11标准中的智能指针 unique_ptr 类的基本使用和一些相关注意事项。不过应该明白,文中的示例使用的deleter是 unique_ptr 的默认 deleter,也即delete方法,这样的局限性实际上很大,原因和解决方法可以参考:shared_ptr自定义deleter 。

欢迎在评论区一起讨论,质疑。文章都是手打原创,每天最浅显的介绍C语言、linux等嵌入式开发,喜欢我的文章就关注一波吧,可以看到最新更新和之前的文章哦。

代码看着不方便的话,可以点击文章末尾的“了解更多”。

"/<task>
/<task>


分享到:


相關文章: