title: C++ 智能指针实现之unique_ptr
description: 智能指针本质上就是利用 RAII 资源管理功能,本文介绍实现 C++中智能指针的 unique_ptr。
date: 2024-01-11T16:40:00+08:00
categories:
- C++
tags: - C++
- 智能指针
keywords: - unique_ptr
- C++智能指针
- C++智能指针原理
image: bg.webp
hidden: false
comments: true
lastmod: 2024-01-11T18:19:07+08:00
前言
智能指针本质上并不神秘,其实就是 RAII 资源管理功能的自然展现而已。本文将介绍如何实现 C++中智能指针的 unique_ptr。
实现过程
首先,unique_ptr 能够包装任意类型,所以,要让这个类能够包装任意类型的指针,我们需要把它变成一个类模板。
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
的行为还是和指针有点差异的:
- 它不能用
*
运算符解引用 - 它不能用
->
运算符指向对象成员 - 它不能像指针一样用在布尔表达式里
不过,这些问题也相当容易解决,加几个成员函数就可以:
template <typename T>
class unique_ptr {
public:
…
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
operator bool() const { return ptr_; }
}
拷贝构造和赋值
最简单的情况显然是禁止拷贝。我们可以使用下面的代码:
template <typename T>
class unique_ptr {
…
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
…
};
禁用这两个函数非常简单,但是,有时我们需要转移所有权,那么这个方案就不可行了。
这里,我们采用移动语义:
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>
。
不过,只需要额外加一点模板代码,就能实现这一行为。在我们目前给出的实现里,只需要增加一个构造函数即可。
template <typename U>
unique_ptr(unique_ptr<U>&& other)
{
ptr_ = other.release();
}
这样,我们自然而然利用了指针的转换特性:现在 unique_ptr<circle>
可以移动给 unique_ptr<shape>
,但不能移动给 unique_ptr<triangle>
。不正确的转换会在代码编译时直接报错。
验证
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,可以
完整代码
#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++编程实战》