C++ 智能指针实现之unique_ptr

前言

智能指针本质上并不神秘,其实就是 RAII 资源管理功能的自然展现而已。本文将介绍如何实现 C++中智能指针的 unique_ptr。

实现过程

首先,unique_ptr 能够包装任意类型,所以,要让这个类能够包装任意类型的指针,我们需要把它变成一个类模板。

cpp 复制代码
template <typename T>
class unique_ptr {
public:
  explicit unique_ptr(T* ptr = nullptr)
    : ptr_(ptr) {}
  ~unique_ptr()
  {
    delete ptr_;
  }
  T* get() const { return ptr_; }
private:
  T* ptr_;
};

目前这个 unique_ptr 的行为还是和指针有点差异的:

  • 它不能用 * 运算符解引用
  • 它不能用 -> 运算符指向对象成员
  • 它不能像指针一样用在布尔表达式里

不过,这些问题也相当容易解决,加几个成员函数就可以:

cpp 复制代码
template <typename T>
class unique_ptr {
public:
  ...
  T& operator*() const { return *ptr_; }
  T* operator->() const { return ptr_; }
  operator bool() const { return ptr_; }
}

拷贝构造和赋值

最简单的情况显然是禁止拷贝。我们可以使用下面的代码:

c++ 复制代码
template <typename T>
class unique_ptr {
  ...
  unique_ptr(const unique_ptr&) = delete;
  unique_ptr& operator=(const unique_ptr&) = delete;
  ...
};

禁用这两个函数非常简单,但是,有时我们需要转移所有权,那么这个方案就不可行了。

这里,我们采用移动语义:

cpp 复制代码
template <typename T>
class unique_ptr {
  ...
  unique_ptr(unique_ptr&& other)
  {
    ptr_ = other.release();
  }
  unique_ptr& operator=(unique_ptr rhs)
  {
    rhs.swap(*this);
    return *this;
  }
  ...
};
  • 把拷贝构造函数中的参数类型 unique_ptr& 改成了 unique_ptr&&;现在它成了移动构造函数。
  • 把赋值函数中的参数类型 unique_ptr& 改成了 unique_ptr,在构造参数时直接生成新的智能指针,从而不再需要在函数体中构造临时对象。现在赋值函数的行为是移动还是拷贝,完全依赖于构造参数时走的是移动构造还是拷贝构造。 根据 C++ 的规则,如果我提供了移动构造函数而没有手动提供拷贝构造函数,那后者自动被禁用。

子类指针向基类指针的转换

一个 circle* 类是可以隐式转换成 shape* 类的,但上面的 unique_ptr<circle> 却无法自动转换成 unique_ptr<shape>

不过,只需要额外加一点模板代码,就能实现这一行为。在我们目前给出的实现里,只需要增加一个构造函数即可。

c++ 复制代码
template <typename U>
unique_ptr(unique_ptr<U>&& other)
{
  ptr_ = other.release();
}

这样,我们自然而然利用了指针的转换特性:现在 unique_ptr<circle> 可以移动给 unique_ptr<shape>,但不能移动给 unique_ptr<triangle>。不正确的转换会在代码编译时直接报错。

验证

cpp 复制代码
unique_ptr<shape> ptr1{create_shape(shape_type::circle)};
unique_ptr<shape> ptr2{ptr1};             // 编译出错
unique_ptr<shape> ptr3;
ptr3 = ptr1;                             // 编译出错
ptr3 = std::move(ptr1);                  // OK,可以
unique_ptr<shape> ptr4{std::move(ptr3)};  // OK,可以

完整代码

cpp 复制代码
#include <utility>

template <typename T>
class unique_ptr {
public:
  explicit unique_ptr(T* ptr = nullptr)
    : ptr_(ptr) {}
  ~unique_ptr()
  {
    delete ptr_;
  }
  unique_ptr(unique_ptr&& other)
  {
    ptr_ = other.release();
  }
  // 子类指针向基类指针的转换
  template <typename U>
  unique_ptr(unique_ptr<U>&& other)
  {
    ptr_ = other.release();
  }
  unique_ptr& operator=(unique_ptr rhs)
  {
    rhs.swap(*this);
    return *this;
  }
  T* release()
  {
    T* ptr = ptr_;
    ptr_ = nullptr;
    return ptr;
  }
  void swap(unique_ptr& rhs)
  {
    using std::swap;
    swap(ptr_, rhs.ptr_);
  }
  T* get() const { return ptr_; }
  T& operator*() const { return *ptr_; }
  T* operator->() const { return ptr_; }
  operator bool() const { return ptr_; }
private:
  T* ptr_;
};

总结

自行实现一个 unique_ptr 相对比较简单,因为不涉及引用计数,只需要一个对象只能被单个 unique_ptr 所拥有。

《现代 C++编程实战》

相关推荐
编码小哥2 分钟前
C++线程同步和互斥
开发语言·c++
打鱼又晒网1 小时前
Linux网络 | 网络计算器客户端实现与Json的安装以及使用
linux·c++·网络协议·计算机网络
DARLING Zero two♡2 小时前
【优选算法】Sliding-Chakra:滑动窗口的算法流(上)
java·开发语言·数据结构·c++·算法
hjxxlsx2 小时前
二维数组综合
c++·算法
小王努力学编程3 小时前
【C++篇】AVL树的实现
java·开发语言·c++
yuanbenshidiaos3 小时前
C++-----图
开发语言·c++·算法
就一枚小白3 小时前
UE--如何用 Python 调用 C++ 及蓝图函数
c++·python·ue5
✿ ༺ ོIT技术༻3 小时前
同步&异步日志系统:设计模式
linux·c++·设计模式
獨枭9 小时前
CMake 构建项目并整理头文件和库文件
c++·github·cmake
小王爱吃月亮糖11 小时前
C++的23种设计模式
开发语言·c++·qt·算法·设计模式·ecmascript