C++11:智能指针

Posted 木大白易

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11:智能指针相关的知识,希望对你有一定的参考价值。

RAII

RAII,全称资源获取即初始化(英语:Resource Acquisition Is Initialization)。
RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄露问题。
传统 C++ 中,『记得』手动释放资源,总不是最佳实践。因为我们很有可能就忘记了去释放资源而导致泄露。 而 C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。 这些智能指针就包括std::shared_ptr/std::unique_ptr/std::weak_ptr,使用它们需要包含头文件 <memory>

智能指针

unique_ptr

只允许基础指针的一个所有者。 可以移到新所有者,但不会复制或共享。 替换已弃用的 auto_ptr。 与 boost::scoped_ptr 比较。 unique_ptr 很小且高效;大小是一个指针,它支持用于从 c + + 标准库集合快速插入和检索的右值引用。
std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全:

// make_unique 从 C++14 引入
std::unique_ptr<int> pointer = std::make_unique<int>(10); 
std::unique_ptr<int> pointer2 = pointer; // 错误,不能共享

auto pointer3 = std::move(pointer); //正确,pointer会断开指向

另外make_unique,可以自己在c++11实现:

template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
  return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}

shared_ptr

采用引用计数的智能指针。 如果你想要将一个原始指针分配给多个所有者(例如,从容器返回了指针副本又想保留原始指针时),请使用该指针。 直至所有 shared_ptr 所有者超出了范围或放弃所有权,才会删除原始指针。 大小为两个指针;一个用于对象,另一个用于包含引用计数的共享控制块。

auto pointer = std::make_shared<Student>("张三");

//或者 显示使用new
shared_ptr<Student> pointer2(new Student("张三"));

std::shared_ptr可以通过 get()方法来获取原始指针,通过 reset()来减少一个引用计数, 并通过use_count()来查看一个对象的引用计数。
举个例子:

auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer; // 引用计数+1
auto pointer3 = pointer; // 引用计数+1
int *p = pointer.get(); // 这样不会增加引用计数
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 3

pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 2
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0, pointer2 已 reset
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 2
pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl; // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 已 reset

weak_ptr

结合 shared_ptr 使用的特例智能指针。 weak_ptr 提供对一个或多个 shared_ptr 实例拥有的对象的访问,但不参与引用计数。 如果你想要观察某个对象但不需要其保持活动状态,请使用该实例。 在某些情况下,需要断开 shared_ptr 实例间的循环引用。

这块可以引用一下坤神的文章:

struct A;
struct B;

struct A {
   std::shared_ptr<B> pointer;
   ~A() {
       std::cout << "A 被销毁" << std::endl;
   }
};
struct B {
   std::shared_ptr<A> pointer;
   ~B() {
       std::cout << "B 被销毁" << std::endl;
   }
};
int main() {
   auto a = std::make_shared<A>();
   auto b = std::make_shared<B>();
   a->pointer = b;
   b->pointer = a;
}

运行结果是 A, B 都不会被销毁,这是因为 a,b 内部的 pointer 同时又引用了 a,b,这使得 a,b 的引用计数均变为了 2,而离开作用域时,a,b 智能指针被析构,却只能造成这块区域的引用计数减一,这样就导致了 a,b 对象指向的内存区域引用计数不为零,而外部已经没有办法找到这块区域了,也就造成了内存泄露,如图 :

解决这个问题的办法就是使用弱引用指针 std::weak_ptr,std::weak_ptr是一种弱引用(相比较而言 std::shared_ptr 就是一种强引用)。弱引用不会引起引用计数增加,当换用弱引用时候,最终的释放流程如图所示:

在上图中,最后一步只剩下 B,而 B 并没有任何智能指针引用它,因此这块内存资源也会被释放。

std::weak_ptr没有*运算符和->运算符,所以不能够对资源进行操作,它的唯一作用就是用于检查std::shared_ptr是否存在,其 expired() 方法能在资源未被释放时,会返回 false,否则返回 true。

参考文章:第 5 章 智能指针与内存管理

以上是关于C++11:智能指针的主要内容,如果未能解决你的问题,请参考以下文章

[C++11]共享智能指针shared_ptr指定删除器

智能指针11

智能指针11

智能指针11

详解C++11智能指针

在条件或循环中分配智能指针