
toc
之前使用智能指针一直是一知半解,现在有时间了,尝试从源码的角度来理解它。
std::unique_ptr 是 C++11 引入的智能指针,用于管理动态分配的内存资源,其核心特性是独占所有权——同一时间只能有一个 unique_ptr 指向特定对象。当 unique_ptr 离开作用域或被销毁时,它所管理的对象会自动释放,从而有效避免内存泄漏。
delete,降低内存泄漏风险unique_ptr 的核心设计思想是独占性,这意味着:
= delete 显式禁用)unique_ptr 被销毁时,其管理的对象也会被自动删除下面通过实现一个简化版的 unique_ptr 来理解其工作原理:
#include <utility> // 用于 std::move
// 默认删除器
template <typename T>
struct DefaultDeleter {
void operator()(T* ptr) const {
delete ptr; // 对单对象使用 delete
}
};
// 数组特化版本的删除器
template <typename T>
struct DefaultDeleter<T[]> {
void operator()(T* ptr) const {
delete[] ptr; // 对数组使用 delete[]
}
};
// 简化版 unique_ptr 实现
template <typename T, typename Deleter = DefaultDeleter<T>>
class UniquePtr {
private:
T* ptr_; // 管理的原始指针
Deleter deleter_; // 删除器对象
public:
// 构造函数:接受原始指针
explicit UniquePtr(T* ptr = nullptr) : ptr_(ptr) {}
// 析构函数:释放资源
~UniquePtr() {
if (ptr_) {
deleter_(ptr_); // 调用删除器释放资源
}
}
// 禁用拷贝构造函数
UniquePtr(const UniquePtr&) = delete;
// 禁用拷贝赋值运算符
UniquePtr& operator=(const UniquePtr&) = delete;
// 移动构造函数:转移所有权
UniquePtr(UniquePtr&& other) noexcept
: ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
other.ptr_ = nullptr; // 源指针置空,避免二次释放
}
// 移动赋值运算符:转移所有权
UniquePtr& operator=(UniquePtr&& other) noexcept {
if (this != &other) {
if (ptr_) {
deleter_(ptr_); // 释放当前资源
}
ptr_ = other.ptr_; // 转移指针
deleter_ = std::move(other.deleter_); // 转移删除器
other.ptr_ = nullptr; // 源指针置空
}
return *this;
}
// 解引用运算符
T& operator*() const {
return *ptr_;
}
// 成员访问运算符
T* operator->() const {
return ptr_;
}
// 数组访问运算符(特化版本中实现)
T& operator[](size_t index) const;
// 获取原始指针
T* get() const {
return ptr_;
}
// 释放所有权
T* release() {
T* temp = ptr_;
ptr_ = nullptr;
return temp;
}
// 重置指针
void reset(T* new_ptr = nullptr) {
if (ptr_) {
deleter_(ptr_); // 释放当前资源
}
ptr_ = new_ptr; // 指向新资源
}
// 交换管理的资源
void swap(UniquePtr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(deleter_, other.deleter_);
}
// 检查是否管理资源
explicit operator bool() const {
return ptr_ != nullptr;
}
};
// 数组版本的 operator[] 实现
template <typename T, typename Deleter>
class UniquePtr<T[], Deleter> {
// 实现与上述类似,但增加数组访问运算符
public:
T& operator[](size_t index) const {
return ptr_[index];
}
// 其他成员函数与单对象版本类似...
};DefaultDeleter,对单对象使用 delete,对数组使用 delete[]&&)实现移动构造和移动赋值= delete 显式禁用拷贝构造和拷贝赋值#include <memory>
#include <iostream>
struct MyClass {
MyClass() { std::cout << "MyClass constructed\n"; }
~MyClass() { std::cout << "MyClass destroyed\n"; }
void do_something() { std::cout << "Doing something\n"; }
};
int main() {
// 创建 unique_ptr,管理动态分配的对象
std::unique_ptr<MyClass> ptr1(new MyClass());
// 使用 make_unique 创建(C++14 引入,更安全)
auto ptr2 = std::make_unique<MyClass>();
// 访问成员函数
ptr1->do_something();
(*ptr2).do_something();
// 检查是否为空
if (ptr1) {
std::cout << "ptr1 is not null\n";
}
// 转移所有权
std::unique_ptr<MyClass> ptr3 = std::move(ptr1);
if (!ptr1) { // ptr1 现在为空
std::cout << "ptr1 is null after move\n";
}
// 释放资源
ptr3.reset(); // 显式释放,此时会调用析构函数
if (!ptr3) {
std::cout << "ptr3 is null after reset\n";
}
return 0;
}unique_ptr 对数组有专门的特化版本,支持 operator[] 访问:
// 管理动态数组
auto arr_ptr = std::make_unique<int[]>(5); // 创建包含5个int的数组
// 访问数组元素
for (int i = 0; i < 5; ++i) {
arr_ptr[i] = i * 10;
std::cout << arr_ptr[i] << " ";
}
// 输出:0 10 20 30 40
// 数组版本会自动使用 delete[] 释放资源当管理非内存资源(如文件句柄、网络套接字)时,可使用自定义删除器:
#include <cstdio>
// 自定义文件删除器
struct FileDeleter {
void operator()(FILE* fp) const {
if (fp) {
std::fclose(fp);
std::cout << "File closed\n";
}
}
};
int main() {
// 使用自定义删除器的 unique_ptr
std::unique_ptr<FILE, FileDeleter> file_ptr(
std::fopen("example.txt", "w"), FileDeleter()
);
if (file_ptr) {
std::fputs("Hello, unique_ptr!", file_ptr.get());
}
// 离开作用域时自动调用 FileDeleter 关闭文件
return 0;
}也可使用 lambda 表达式作为删除器:
auto ptr = std::unique_ptr<MyClass, void(*)(MyClass*)>(
new MyClass(),
[](MyClass* p) {
std::cout << "Custom deleter called\n";
delete p;
}
);unique_ptr 支持基类指针指向派生类对象,实现多态:
struct Base {
virtual void foo() { std::cout << "Base::foo\n"; }
virtual ~Base() = default; // 基类析构函数必须为虚函数
};
struct Derived : Base {
void foo() override { std::cout << "Derived::foo\n"; }
};
int main() {
std::unique_ptr<Base> base_ptr = std::make_unique<Derived>();
base_ptr->foo(); // 多态调用,输出 "Derived::foo"
return 0;
}注意:基类析构函数必须为虚函数,否则会导致未定义行为。
unique_ptr 可作为函数返回值,自动转移所有权:
std::unique_ptr<MyClass> create_object() {
return std::make_unique<MyClass>(); // 自动移动返回
}
int main() {
auto obj = create_object(); // 接收返回的 unique_ptr
obj->do_something();
return 0;
}unique_ptr 可存储在支持移动语义的容器中(如 std::vector):
#include <vector>
int main() {
std::vector<std::unique_ptr<MyClass>> objects;
// 添加元素(需要使用 std::move)
objects.push_back(std::make_unique<MyClass>());
objects.emplace_back(new MyClass()); // 更高效
// 遍历容器
for (const auto& ptr : objects) {
ptr->do_something();
}
return 0;
}unique_ptr 非常适合实现 PImpl(Pointer to Implementation)惯用法,隐藏实现细节:
// 头文件 MyClass.h
class MyClass {
private:
// 前向声明实现类
class Impl;
std::unique_ptr<Impl> pimpl; // 指向实现的 unique_ptr
public:
MyClass();
~MyClass(); // 需要在 cpp 文件中定义,因为 Impl 在此处不完整
void do_something();
};
// 实现文件 MyClass.cpp
class MyClass::Impl {
public:
void do_something() {
// 实际实现
}
};
MyClass::MyClass() : pimpl(std::make_unique<Impl>()) {}
MyClass::~MyClass() = default; // 此时 Impl 已完整定义
void MyClass::do_something() { pimpl->do_something(); } // 错误示例:多个 unique_ptr 管理同一资源
int* raw_ptr = new int(10);
std::unique_ptr<int> ptr1(raw_ptr);
std::unique_ptr<int> ptr2(raw_ptr); // 严重错误!双重释放 // 错误示例:手动释放导致二次释放
auto ptr = std::make_unique<int>(10);
delete ptr.get(); // 错误!unique_ptr 析构时会再次释放 // 错误示例:C 函数不知道 unique_ptr 的存在
void c_function(int* p) { /* ... */ }
auto ptr = std::make_unique<int>(10);
c_function(ptr.release()); // 正确:释放所有权
// c_function(ptr.get()); // 危险:如果函数存储了指针会导致问题 auto ptr1 = std::make_unique<MyClass>(); // 推荐
auto ptr2 = std::unique_ptr<MyClass>(new MyClass()); // 不推荐 auto ptr1 = std::make_unique<MyClass>();
auto ptr2 = std::move(ptr1); // 正确:转移所有权
// auto ptr3 = ptr2; // 错误:禁止拷贝unique_ptr<T[]> 特化版本unique_ptr<T> 管理数组unique_ptr 体积会增大std::unique_ptr 是 C++ 中管理独占资源的首选智能指针,通过独占所有权和移动语义,在保证性能的同时有效避免内存泄漏。其核心特点包括:
unique_ptr 管理资源在实际开发中,应优先考虑使用 unique_ptr 而非原始指针,只有在需要共享所有权时才考虑 std::shared_ptr。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。