C++ - 简单实现std::unique_ptr
我们手动实现一个简化版的unique_ptr,它将拥有独占所有权,并且支持移动语义,但不支持拷贝语义。
我们将实现以下功能:
-
构造函数:从原始指针构造
-
析构函数:释放资源
-
移动构造函数:转移所有权
-
移动赋值运算符:转移所有权
-
禁止拷贝构造和拷贝赋值
-
重载 operator* 和 operator->
-
实现 release() 和 reset() 方法
-
实现 get() 方法
我们不会实现自定义删除器,以保持简单。
我们要实现以下核心功能:
-
独占所有权(禁止拷贝)
-
移动语义
-
资源自动释放
-
解引用操作
MyUniquePtr.h 实现代码:
#include <iostream>
#include <utility> // for std::swap, std::move
template<typename T>
class MyUniquePtr {
private:
T* m_ptr; // 托管对象的原始指针
public:
// 1. 默认构造函数
MyUniquePtr() noexcept : m_ptr(nullptr) {}
// 2. 从原始指针构造(显式,避免隐式转换)
explicit MyUniquePtr(T* ptr) noexcept : m_ptr(ptr) {
std::cout << "MyUniquePtr 构造函数" << std::endl;
}
// 3. 禁止拷贝构造 - 核心特性!
MyUniquePtr(const MyUniquePtr&) = delete;
// 4. 禁止拷贝赋值 - 核心特性!
MyUniquePtr& operator=(const MyUniquePtr&) = delete;
// 5. 移动构造函数 - 转移所有权
MyUniquePtr(MyUniquePtr&& other) noexcept : m_ptr(other.release()) {
std::cout << "MyUniquePtr 移动构造函数" << std::endl;
}
// 6. 移动赋值运算符 - 转移所有权
MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
std::cout << "MyUniquePtr 移动赋值运算符" << std::endl;
if (this != &other) {
reset(other.release()); // 先释放当前资源,再接管新资源
}
return *this;
}
// 7. 析构函数
~MyUniquePtr() {
std::cout << "MyUniquePtr 析构函数" << std::endl;
reset();
}
// 8. 解引用操作符
T& operator*() const noexcept {
return *m_ptr;
}
// 9. 箭头操作符
T* operator->() const noexcept {
return m_ptr;
}
// 10. 获取原始指针
T* get() const noexcept {
return m_ptr;
}
// 11. 释放所有权(返回指针并将内部指针置为空)
T* release() noexcept {
T* ptr = m_ptr;
m_ptr = nullptr;
return ptr;
}
// 12. 重置资源(删除当前对象,可选的接管新对象)
void reset(T* ptr = nullptr) noexcept {
// 删除当前管理的对象(如果存在)
if (m_ptr) {
std::cout << "删除托管对象" << std::endl;
delete m_ptr;
}
m_ptr = ptr;
}
// 13. 交换两个unique_ptr
void swap(MyUniquePtr& other) noexcept {
std::swap(m_ptr, other.m_ptr);
}
// 14. 布尔转换(用于条件判断)
explicit operator bool() const noexcept {
return m_ptr != nullptr;
}
// 15. 与nullptr比较
bool operator==(std::nullptr_t) const noexcept {
return m_ptr == nullptr;
}
bool operator!=(std::nullptr_t) const noexcept {
return m_ptr != nullptr;
}
};
// 全局swap函数
template<typename T>
void swap(MyUniquePtr<T>& lhs, MyUniquePtr<T>& rhs) noexcept {
lhs.swap(rhs);
}
核心原理详解
1. 独占所有权实现
// 关键:删除拷贝构造和拷贝赋值
MyUniquePtr(const MyUniquePtr&) = delete;
MyUniquePtr& operator=(const MyUniquePtr&) = delete;
2. 移动语义实现
// 移动构造:转移所有权
MyUniquePtr(MyUniquePtr&& other) noexcept : m_ptr(other.release()) {}
// 移动赋值:先释放当前资源,再接管新资源
MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
if (this != &other) {
reset(other.release());
}
return *this;
}
3. 资源管理核心
void reset(T* ptr = nullptr) noexcept {
if (m_ptr) {
delete m_ptr; // 释放当前资源
}
m_ptr = ptr; // 接管新资源(可能为nullptr)
}
T* release() noexcept {
T* ptr = m_ptr;
m_ptr = nullptr; // 放弃所有权但不删除
return ptr;
}
测试代码
main.cpp
#include <iostream>
#include "MyUniquePtr.h"
// 测试类
class TestResource {
public:
TestResource(const std::string& name) : m_name(name) {
std::cout << "TestResource 构造: " << m_name << std::endl;
}
~TestResource() {
std::cout << "TestResource 析构: " << m_name << std::endl;
}
void print() const {
std::cout << "TestResource::print(): " << m_name << std::endl;
}
std::string getName() const {
return m_name;
}
private:
std::string m_name;
};
void test_basic_lifecycle() {
std::cout << "=== 测试1: 基本生命周期 ===" << std::endl;
MyUniquePtr<TestResource> ptr1(new TestResource("Resource1"));
ptr1->print();
// 离开作用域时自动销毁
}
void test_move_semantics() {
std::cout << "\n=== 测试2: 移动语义 ===" << std::endl;
MyUniquePtr<TestResource> ptr1(new TestResource("Resource2"));
// 移动构造
MyUniquePtr<TestResource> ptr2 = std::move(ptr1);
std::cout << "移动构造后:" << std::endl;
std::cout << "ptr1 is null: " << (ptr1 == nullptr) << std::endl;
std::cout << "ptr2 getName: " << ptr2->getName() << std::endl;
// 移动赋值
MyUniquePtr<TestResource> ptr3(new TestResource("Resource3"));
ptr3 = std::move(ptr2);
std::cout << "移动赋值后:" << std::endl;
std::cout << "ptr2 is null: " << (ptr2 == nullptr) << std::endl;
std::cout << "ptr3 getName: " << ptr3->getName() << std::endl;
}
void test_release_reset() {
std::cout << "\n=== 测试3: release和reset ===" << std::endl;
MyUniquePtr<TestResource> ptr1(new TestResource("Resource4"));
// release: 释放所有权但不删除
TestResource* raw_ptr = ptr1.release();
std::cout << "release后ptr1 is null: " << (ptr1 == nullptr) << std::endl;
std::cout << "raw_ptr getName: " << raw_ptr->getName() << std::endl;
// 需要手动管理原始指针
delete raw_ptr;
// reset: 重新设置管理的对象
ptr1.reset(new TestResource("Resource5"));
std::cout << "reset后ptr1 getName: " << ptr1->getName() << std::endl;
}
void test_boolean_conversion() {
std::cout << "\n=== 测试4: 布尔转换 ===" << std::endl;
MyUniquePtr<TestResource> ptr1(new TestResource("Resource6"));
MyUniquePtr<TestResource> ptr2; // 空指针
if (ptr1) {
std::cout << "ptr1 包含有效指针" << std::endl;
}
if (!ptr2) {
std::cout << "ptr2 是空指针" << std::endl;
}
}
// 测试拷贝禁止(编译时会报错)
void test_copy_prohibition() {
std::cout << "\n=== 测试5: 拷贝禁止(这行代码会编译错误) ===" << std::endl;
MyUniquePtr<TestResource> ptr1(new TestResource("Resource7"));
// 取消下面两行的注释会导致编译错误
// MyUniquePtr<TestResource> ptr2 = ptr1; // 错误:拷贝构造被删除
// MyUniquePtr<TestResource> ptr3;
// ptr3 = ptr1; // 错误:拷贝赋值被删除
}
int main()
{
test_basic_lifecycle();
test_move_semantics();
test_release_reset();
test_boolean_conversion();
test_copy_prohibition();
return 0;
}
运行结果:

与标准库的差异
我们的简化版本缺少了标准库的以下特性:
-
自定义删除器 - 标准库支持自定义删除策略
-
数组特化 -
unique_ptr<T[]>对数组的特殊处理 -
更完善的异常安全 - 标准库有更精细的异常处理
-
与weak_ptr的兼容性 - 标准库中unique_ptr不能与weak_ptr配合
通过这个实现,你深入理解了:
-
独占所有权语义 - 为什么unique_ptr不可拷贝
-
移动语义的应用 - 如何通过移动来转移所有权
-
RAII模式 - 资源获取即初始化
-
显式所有权转移 -
release()和reset()的用途 -
资源安全 - 自动释放避免内存泄漏

浙公网安备 33010602011771号