C++手写智能指针
1.普通指针的问题
普通指针(裸指针)在C++中提供了强大的灵活性,但也带来了诸多问题:
-
内存泄漏:忘记释放动态分配的内存。
-
悬空指针:访问已释放的内存区域。
-
双重释放:多次释放同一块内存。
-
所有权不明确:不知道谁应该负责释放内存。
void problemExample() {int* raw_ptr = new int(42); // 动态分配// 如果这里发生异常或提前返回,内存将泄漏if (some_condition) return;delete raw_ptr; // 必须手动释放 }
2. C++智能指针
智能指针是C++标准库(在 <memory>
头文件中)提供的类模板,它们通过RAII(Resource Acquisition Is Initialization) 机制来管理动态分配的内存(或其他资源)。
核心思想: 将裸指针(raw pointer)封装在一个对象中。当这个对象离开其作用域被销毁时,它的析构函数会自动调用 delete
或 delete[]
来释放它所拥有的内存。这有效地防止了由于忘记手动释放内存而导致的内存泄漏。
C++11引入了三种主要的智能指针:
2.1 std::unique_ptr
-
独占所有权,不能拷贝只能移动
同一时间只能有一个
unique_ptr
拥有某个对象。当unique_ptr
被销毁时,它指向的对象也会被自动销毁。它不能被复制,只能被移动(std::move
)。 -
轻量级,零额外开销(与裸指针相同)
-
离开作用域时自动释放内存
2.2 std::shared_ptr
-
共享所有权,使用引用计数
多个
shared_ptr
可以同时“拥有”同一个对象。它通过引用计数机制来跟踪有多少个shared_ptr
共享同一对象。当最后一个shared_ptr
被销毁时,对象才会被释放。 -
多个指针可以指向同一对象
-
当最后一个引用被销毁时释放内存
-
线程安全:引用计数的操作是原子性的(线程安全)
-
循环引用问题:需要与
std::weak_ptr
配合使用
2.3 std::weak_ptr
-
不增加引用计数的共享指针
-
解决
shared_ptr
的循环引用问题 -
需要转换为
shared_ptr
才能访问对象
3. 手动实现简化版智能指针
3.1 实现SimpleUniquePtr,它模仿 std::unique_ptr
的独占所有权特性。
#include <iostream>
#include <utility> // for std::swaptemplate <typename T>
class SimpleUniquePtr {
private:T* ptr_ = nullptr;// 防止拷贝SimpleUniquePtr(const SimpleUniquePtr&) = delete;SimpleUniquePtr& operator=(const SimpleUniquePtr&) = delete;public:// 默认构造函数SimpleUniquePtr() = default;// 构造函数,接管裸指针explicit SimpleUniquePtr(T* ptr) : ptr_(ptr) {}// 移动构造函数:接管另一个智能指针的资源,并将原指针置空SimpleUniquePtr(SimpleUniquePtr&& other) noexcept : ptr_(other.ptr_) {other.ptr_ = nullptr;}// 移动赋值运算符SimpleUniquePtr& operator=(SimpleUniquePtr&& other) noexcept {// 先清理自己当前拥有的资源delete ptr_;// 接管对方的资源ptr_ = other.ptr_;// 将对方的指针置空other.ptr_ = nullptr;return *this;}// 析构函数:释放资源~SimpleUniquePtr() {delete ptr_;}// 重载 * 运算符,解引用T& operator*() const {return *ptr_;}// 重载 -> 运算符,访问成员T* operator->() const {return ptr_;}// 获取被封装的裸指针(但不释放所有权)T* get() const {return ptr_;}// 释放资源的所有权,返回裸指针,并将内部指针置为空T* release() {T* released_ptr = ptr_;ptr_ = nullptr;return released_ptr;}// 重置资源:先删除当前拥有的,再接管新的void reset(T* new_ptr = nullptr) {// 防止自我重置时先被删除if (ptr_ != new_ptr) {delete ptr_;ptr_ = new_ptr;}}// 交换两个智能指针void swap(SimpleUniquePtr& other) noexcept {using std::swap;swap(ptr_, other.ptr_);}// 检查是否拥有有效对象explicit operator bool() const {return ptr_ != nullptr;}
};// 为我们的智能指针特化 std::swap,提供高效交换
template <typename T>
void swap(SimpleUniquePtr<T>& lhs, SimpleUniquePtr<T>& rhs) noexcept {lhs.swap(rhs);
}// --- 示例使用 ---
class MyClass {
public:MyClass() { std::cout << "MyClass constructed\n"; }~MyClass() { std::cout << "MyClass destroyed\n"; }void sayHello() { std::cout << "Hello from MyClass!\n"; }
};int main() {std::cout << "Creating SimpleUniquePtr...\n";SimpleUniquePtr<MyClass> ptr1(new MyClass()); // 构造函数ptr1->sayHello(); // 使用 -> 运算符(*ptr1).sayHello(); // 使用 * 运算符std::cout << "Moving ownership to ptr2...\n";SimpleUniquePtr<MyClass> ptr2 = std::move(ptr1); // 使用移动构造函数if (!ptr1) { // 使用 bool 转换std::cout << "ptr1 is now empty.\n";}if (ptr2) {std::cout << "ptr2 owns the object.\n";ptr2->sayHello();}std::cout << "Exiting main scope...\n";// ptr2 离开作用域,析构函数自动调用 delete,MyClass 被销毁// ptr1 是空的,析构函数什么都不做return 0;
}
输出结果: