std::unique_ptr 关于智能指针的种种

这个域名也是突发奇想买到的,作为unique-ptr.com,为了对得起这个名儿,那就说一下智能指针算了。

前传。裸指针和auto_ptr

裸指针就是高度自治(要自己管)的指针,在符合基本法(误)的前提下,使用裸指针的人可以为所欲为。裸指针的一个问题是,它有点不符合现代C++内存管理的指导思想,使用者必须对它的生命周期负责,否则有可能发生:

  • 内存泄漏(memory leak):如果new一大片内存但是由于代码复杂到头来忘了释放,就有可能耗尽内存资源;内存泄漏还有一种情况:如果申请内存做事情做了一半抛出了异常——代码跑到了exception的花括号里之后,原来申请的内存直接没办法拿到并且释放;
  • 悬挂指针(dangling reference):指针已经被删除了,但其内存仍然在使用中;另外还有双重删除(double delete)的问题,当程序的某部分要删除一个已经被删除的指针时,即可出现这种情况;还有一种,指针所指的内存已经失效了(比如出了作用域了),但是后面的代码还用着指针;

应该是为了缓解这类问题,C++ 98标准搞出来了个智能指针:auto_ptr。相当于加了一个“管家”,在对象的内部保存那个裸指针,在管家析构的时候自动释放裸指针的内容,如果赋值的话那就把裸指针的所有权转交给新的管家。
但是由于移动(move)语义是C++11才有的,auto_ptr在实现的时候,如果在两个智能指针对象之间作赋值(operator=)或者拷贝构造,它的参数都是非const类型的。为啥呢?因为它是会调用original_auto_ptr.release()方法拿到原始裸指针。
这个有点无奈的操作是为了实现指针所指对象的“占有权”管理:两个auto_ptr不允许管理同一个目标裸指针。在“拷贝”的时候,相当于做了“主权移交”。

Copying an auto_ptr copies the pointer and transfers ownership to the
destination: both copy construction and copy assignment of auto_ptr
modify their right hand arguments, and the “copy” is not equal to the
original.
https://en.cppreference.com/w/cpp/memory/auto_ptr

这种操作并不符合常人对“拷贝“的理解,更像是”剪切“操作。且正是因为这个问题,切不可把auto_ptr放到stl容器里面,否则会得到各种编译错误。典型案例是这个:https://stackoverflow.com/questions/111478/why-is-it-wrong-to-use-stdauto-ptr-with-standard-containers
另外由于析构的时候是用delete,auto_ptr里也不能放指针数组(那种需要用delete[]).

unique_ptr

不同于auto_ptr,unique_ptr 的赋值运算符只接受典型地由 std::move 生成的右值。刚才说了,“右值”是C++11移动语义的产物,它比较好地解决了主权移交时的尴尬问题。这怎么说呢,一个智能指针的命运啊,当然要靠自我奋斗,但是也要考虑到历史的行程
回顾看之前说的裸指针的两个问题:

  • 内存泄漏:由于析构函数里会delete管理的裸指针对象,所以在跑出对象作用域的时候,编译器会帮我们解决好问题,不用操心了;
  • 悬挂指针:缓解,但不能完全避免。

一个例子是

#include <iostream>
#include <memory>
#include <string>
    
int main()
{
  std::unique_ptr<std::string> str_ptr;
  {
      std::string tmp("bbbababa");
      str_ptr.reset(&tmp);
      std::cout << str_ptr.get() << std::endl;
      std::cout << *str_ptr << std::endl;
  }
  std::string tmp("xxxxxxx");
  std::cout << str_ptr.get() << std::endl;
  std::cout << *str_ptr << std::endl;
  return 0;
}
// Output:
//   0x728562b4f060
//   bbbababa
//   0x728562b4f060
//   xxxxxxx

此外,数组的问题也通过特化std::unique_ptr<T[]> 得到了解决.
可能有些同学会有性能方面的顾虑,觉得智能指针会比裸指针的操作性能差一点。坦白讲这个是有可能的,但是在实际使用的过程中,往往作profiling后会发现性能出现瓶颈的地方几乎不是智能指针。作为unique_ptr来讲,它不需要维护额外的状态,只是对象的创建和销毁要作一些特殊处理,平时使用的过程中完全可以当作裸指针来用。真正可能要担心性能问题的,是shared_ptr这一类允许共享所有权的智能指针。

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.