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++编程实战》

相关推荐
All for pursuit.12 分钟前
【链表-9】146.LRU缓存
数据结构·c++·算法·leetcode
j7~1 小时前
【C++微服务项目开发脚手架】(接口篇一)gflags + gtest + spdlog 接口学习笔记
c++·学习·gtest·项目开发·spdlog·gflags·c++项目微服务开发脚手架
星期八不上发条1 小时前
智能指针是什么?使用场景,循环引用解决办法,面试回答
开发语言·c++·stl
ao-weilai2 小时前
Linux网络编程:Socket UDP
linux·服务器·网络·c++
.YM.Z3 小时前
C++——【继承】的底层逻辑与【多态】实现机制
c++·多态·继承
汉克老师3 小时前
GESP2026年9月认证C++三级( 第一部分选择题(1~7题)精讲
c++·gesp·小学生·学c++编程
小小龙学IT4 小时前
Qt QPainter 2D 绘图系统深度解析:从画笔到渲染引擎适
c++·qt
wabs6665 小时前
关于二叉树【力扣100.相同的树的思考】
数据结构·c++·算法·leetcode·二叉树·递归法
小小龙学IT5 小时前
Boost.PFR 开源结构体字段反射库深度解析
c++·开源
程序猿编码5 小时前
零依赖纯手写:C++ 实现完整神经网络,张量反向传播全打通
c++·神经网络·transformer·大模型推理