More Effective C++ 条款28:智能指针
More Effective C++ 条款28:智能指针
核心思想:通过封装原始指针并在对象生命周期结束时自动释放资源,智能指针实现了RAII原则,有效防止内存泄漏和资源管理错误,同时提供异常安全保证。
🚀 1. 问题本质分析
1.1 原始指针的缺陷:
- 内存泄漏风险:需要手动管理内存,容易忘记释放
- 异常不安全:在异常发生时,资源可能无法正确释放
- 所有权模糊:难以确定指针所有权和生命周期责任
- 悬空指针:可能访问已释放的内存区域
1.2 智能指针的核心需求:
- 自动资源管理:在适当时候自动释放资源
- 所有权语义:明确资源的所有权关系(独占、共享等)
- 异常安全:保证在异常发生时资源能被正确释放
- 指针语义:提供与原始指针类似的接口(解引用、箭头操作等)
// 基础示例:简单智能指针实现
template<typename T>
class AutoPtr {
public:explicit AutoPtr(T* ptr = nullptr) : m_ptr(ptr) {}~AutoPtr() {delete m_ptr;}// 重载解引用操作符T& operator*() const { return *m_ptr; }// 重载箭头操作符T* operator->() const { return m_ptr; }// 禁止拷贝(独占所有权)AutoPtr(const AutoPtr&) = delete;AutoPtr& operator=(const AutoPtr&) = delete;private:T* m_ptr;
};
📦 2. 问题深度解析
2.1 所有权语义的实现:
// 独占所有权智能指针(类似std::unique_ptr)
template<typename T>
class UniquePtr {
public:explicit UniquePtr(T* ptr = nullptr) : m_ptr(ptr) {}~UniquePtr() {delete m_ptr;}// 移动构造函数UniquePtr(UniquePtr&& other) noexcept : m_ptr(other.release()) {}// 移动赋值运算符UniquePtr& operator=(UniquePtr&& other) noexcept {reset(other.release());return *this;}// 释放所有权T* release() {T* ptr = m_ptr;m_ptr = nullptr;return ptr;}// 重置指针void reset(T* ptr = nullptr) {delete m_ptr;m_ptr = ptr;}// 指针操作T& operator*() const { return *m_ptr; }T* operator->() const { return m_ptr; }explicit operator bool() const { return m_ptr != nullptr; }// 禁止拷贝UniquePtr(const UniquePtr&) = delete;UniquePtr& operator=(const UniquePtr&) = delete;private:T* m_ptr;
};
2.2 引用计数共享所有权:
// 共享所有权智能指针(类似std::shared_ptr)
template<typename T>
class SharedPtr {
public:explicit SharedPtr(T* ptr = nullptr) : m_ptr(ptr), m_refCount(new size_t(1)) {}// 拷贝构造函数SharedPtr(const SharedPtr& other) : m_ptr(other.m_ptr), m_refCount(other.m_refCount) {++(*m_refCount);}// 拷贝赋值运算符SharedPtr& operator=(const SharedPtr& other) {if (this != &other) {// 减少当前引用计数decrementRefCount();// 复制指针和引用计数m_ptr = other.m_ptr;m_refCount = other.m_refCount;++(*m_refCount);}return *this;}// 移动语义SharedPtr(SharedPtr&& other) noexcept : m_ptr(other.m_ptr), m_refCount(other.m_refCount) {other.m_ptr = nullptr;other.m_refCount = nullptr;}SharedPtr& operator=(SharedPtr&& other) noexcept {if (this != &other) {decrementRefCount();m_ptr = other.m_ptr;m_refCount = other.m_refCount;other.m_ptr = nullptr;other.m_refCount = nullptr;}return *this;}~SharedPtr() {decrementRefCount();}// 指针操作T& operator*() const { return *m_ptr; }T* operator->() const { return m_ptr; }explicit operator bool() const { return m_ptr != nullptr; }size_t use_count() const { return m_refCount ? *m_refCount : 0; }private:void decrementRefCount() {if (m_refCount) {--(*m_refCount);if (*m_refCount == 0) {delete m_ptr;delete m_refCount;}}}T* m_ptr;size_t* m_refCount; // 引用计数
};
2.3 弱引用指针实现:
// 弱引用指针(类似std::weak_ptr)
template<typename T>
class WeakPtr {
public:WeakPtr() : m_ptr(nullptr), m_refCount(nullptr) {}// 从SharedPtr构造WeakPtr(const SharedPtr<T>& shared) : m_ptr(shared.m_ptr), m_refCount(shared.m_refCount) {}// 拷贝构造函数WeakPtr(const WeakPtr& other) : m_ptr(other.m_ptr), m_refCount(other.m_refCount) {}// 拷贝赋值WeakPtr& operator=(const WeakPtr& other) {m_ptr = other.m_ptr;m_refCount = other.m_refCount;return *this;}// 从SharedPtr赋值WeakPtr& operator=(const SharedPtr<T>& shared) {m_ptr = shared.m_ptr;m_refCount = shared.m_refCount;return *this;}// 尝试提升为SharedPtrSharedPtr<T> lock() const {if (m_refCount && *m_refCount > 0) {return SharedPtr<T>(*this);}return SharedPtr<T>();}// 检查是否过期bool expired() const {return !m_refCount || *m_refCount == 0;}private:T* m_ptr;size_t* m_refCount;// SharedPtr需要能访问WeakPtr的私有成员friend class SharedPtr<T>;
};
⚖️ 3. 解决方案与最佳实践
3.1 自定义删除器支持:
// 支持自定义删除器的UniquePtr
template<typename T, typename Deleter = std::default_delete<T>>
class UniquePtrWithDeleter {
public:explicit UniquePtrWithDeleter(T* ptr = nullptr, Deleter deleter = Deleter()): m_ptr(ptr), m_deleter(std::move(deleter)) {}~UniquePtrWithDeleter() {if (m_ptr) {m_deleter(m_ptr);}}// 移动构造函数UniquePtrWithDeleter(UniquePtrWithDeleter&& other) noexcept: m_ptr(other.release()), m_deleter(std::move(other.m_deleter)) {}// 移动赋值运算符UniquePtrWithDeleter& operator=(UniquePtrWithDeleter&& other) noexcept {reset(other.release());m_deleter = std::move(other.m_deleter);return *this;}// 释放所有权T* release() {T* ptr = m_ptr;m_ptr = nullptr;return ptr;}// 重置指针void reset(T* ptr = nullptr) {if (m_ptr) {m_deleter(m_ptr);}m_ptr = ptr;}// 指针操作T& operator*() const { return *m_ptr; }T* operator->() const { return m_ptr; }explicit operator bool() const { return m_ptr != nullptr; }// 禁止拷贝UniquePtrWithDeleter(const UniquePtrWithDeleter&) = delete;UniquePtrWithDeleter& operator=(const UniquePtrWithDeleter&) = delete;private:T* m_ptr;Deleter m_deleter;
};// 自定义删除器示例
struct FileDeleter {void operator()(FILE* file) {if (file) {fclose(file);}}
};// 使用示例
void fileExample() {UniquePtrWithDeleter<FILE, FileDeleter> file(fopen("test.txt", "r"));if (file) {// 使用文件char buffer[100];fgets(buffer, 100, file.get());}// 文件自动关闭
}
3.2 异常安全的资源管理:
// 使用智能指针确保异常安全
class DatabaseConnection {
public:static UniquePtr<DatabaseConnection> create(const std::string& connectionString) {// 可能抛出异常的连接操作DatabaseConnection* rawPtr = new DatabaseConnection();try {rawPtr->connect(connectionString);return UniquePtr<DatabaseConnection>(rawPtr);} catch (...) {delete rawPtr;throw;}}~DatabaseConnection() {if (connected) {disconnect();}}void executeQuery(const std::string& query) {// 执行查询,可能抛出异常if (!connected) {throw std::runtime_error("Not connected");}// 模拟可能抛出异常的操作if (query.empty()) {throw std::invalid_argument("Empty query");}// 执行查询...}private:DatabaseConnection() : connected(false) {}void connect(const std::string& connectionString) {// 连接操作,可能抛出异常if (connectionString.empty()) {throw std::invalid_argument("Empty connection string");}// 模拟连接操作connected = true;}void disconnect() {// 断开连接connected = false;}bool connected;
};// 使用示例
void databaseExample() {try {auto db = DatabaseConnection::create("server=localhost;database=test");db->executeQuery("SELECT * FROM users");// 即使抛出异常,连接也会正确关闭} catch (const std::exception& e) {std::cerr << "Database error: " << e.what() << std::endl;}
}
3.3 循环引用与弱指针解决方案:
// 循环引用问题示例与解决方案
class Node {
public:std::string name;// 使用SharedPtr会导致循环引用// SharedPtr<Node> parent;// std::vector<SharedPtr<Node>> children;// 解决方案:父节点使用WeakPtrWeakPtr<Node> parent;std::vector<SharedPtr<Node>> children;Node(const std::string& nodeName) : name(nodeName) {}void addChild(const SharedPtr<Node>& child) {children.push_back(child);child->parent = SharedPtr<Node>(this); // 注意:这里有问题,应该使用shared_from_this}~Node() {std::cout << "Destroying node: " << name << std::endl;}
};// 正确实现:使用enable_shared_from_this
class TreeNode : public std::enable_shared_from_this<TreeNode> {
public:std::string name;WeakPtr<TreeNode> parent;std::vector<SharedPtr<TreeNode>> children;static SharedPtr<TreeNode> create(const std::string& nodeName) {return SharedPtr<TreeNode>(new TreeNode(nodeName));}void addChild(const SharedPtr<TreeNode>& child) {children.push_back(child);child->parent = shared_from_this(); // 正确获取自身的shared_ptr}~TreeNode() {std::cout << "Destroying TreeNode: " << name << std::endl;}private:TreeNode(const std::string& nodeName) : name(nodeName) {}
};// 使用示例
void treeExample() {auto root = TreeNode::create("root");auto child1 = TreeNode::create("child1");auto child2 = TreeNode::create("child2");root->addChild(child1);root->addChild(child2);// 没有循环引用,所有节点都能正确销毁
}
3.4 智能指针与多线程安全:
// 线程安全的引用计数实现
template<typename T>
class ThreadSafeSharedPtr {
public:explicit ThreadSafeSharedPtr(T* ptr = nullptr) : m_ptr(ptr), m_refCount(new std::atomic<size_t>(1)) {}// 拷贝构造函数ThreadSafeSharedPtr(const ThreadSafeSharedPtr& other) {std::lock_guard<std::mutex> lock(other.m_mutex);m_ptr = other.m_ptr;m_refCount = other.m_refCount;m_mutex = other.m_mutex;m_refCount->fetch_add(1, std::memory_order_relaxed);}// 拷贝赋值运算符ThreadSafeSharedPtr& operator=(const ThreadSafeSharedPtr& other) {if (this != &other) {// 先增加其他对象的引用计数std::atomic<size_t>* otherRefCount;{std::lock_guard<std::mutex> lock(other.m_mutex);otherRefCount = other.m_refCount;otherRefCount->fetch_add(1, std::memory_order_relaxed);}// 然后减少当前对象的引用计数decrementRefCount();// 复制指针和引用计数std::lock_guard<std::mutex> lock(m_mutex);m_ptr = other.m_ptr;m_refCount = otherRefCount;}return *this;}~ThreadSafeSharedPtr() {decrementRefCount();}// 指针操作(需要线程安全访问)T& operator*() const { std::lock_guard<std::mutex> lock(m_mutex);return *m_ptr; }T* operator->() const {std::lock_guard<std::mutex> lock(m_mutex);return m_ptr;}size_t use_count() const { std::lock_guard<std::mutex> lock(m_mutex);return m_refCount->load(std::memory_order_relaxed); }private:void decrementRefCount() {std::lock_guard<std::mutex> lock(m_mutex);if (m_refCount) {size_t oldCount = m_refCount->fetch_sub(1, std::memory_order_acq_rel);if (oldCount == 1) {delete m_ptr;delete m_refCount;m_ptr = nullptr;m_refCount = nullptr;}}}T* m_ptr;std::atomic<size_t>* m_refCount;mutable std::mutex m_mutex;
};
💡 关键实践原则
- 优先使用智能指针
在现代C++中,应优先使用std::unique_ptr和std::shared_ptr,避免使用原始指针管理资源 - 明确所有权语义
根据需求选择合适的智能指针:独占所有权用unique_ptr,共享所有权用shared_ptr,观察用weak_ptr - 注意循环引用
使用shared_ptr时要注意可能产生的循环引用问题,并使用weak_ptr打破循环 - 考虑线程安全
在多线程环境中使用智能指针时,需要确保引用计数的原子性和数据访问的同步 - 使用自定义删除器
对于特殊资源(文件、网络连接等),使用自定义删除器确保正确释放
智能指针与多态:
// 智能指针支持多态 class Base { public:virtual ~Base() = default;virtual void operation() = 0; };class Derived : public Base { public:void operation() override {std::cout << "Derived operation" << std::endl;} };void polymorphismExample() {std::unique_ptr<Base> ptr = std::make_unique<Derived>();ptr->operation(); // 正确调用Derived的实现// unique_ptr可以正确调用派生类的析构函数 }
智能指针与STL容器:
// 在容器中安全存储动态分配的对象 void containerExample() {std::vector<std::unique_ptr<Base>> objects;// 使用emplace_back直接构造智能指针objects.emplace_back(std::make_unique<Derived>());objects.emplace_back(std::make_unique<Derived>());// 转移所有权到容器中auto newObj = std::make_unique<Derived>();objects.push_back(std::move(newObj));// 遍历容器for (const auto& obj : objects) {obj->operation();}// 容器清空时,所有对象自动释放 }
总结:
智能指针是C++资源管理的核心工具,通过封装资源生命周期管理,实现了RAII原则,有效防止资源泄漏和内存错误。
不同类型的智能指针提供了不同的所有权语义:unique_ptr用于独占所有权,shared_ptr用于共享所有权,weak_ptr用于打破循环引用。现代C++开发中应优先使用标准库智能指针,避免手动资源管理。
正确使用智能指针可以大幅提高代码的可靠性、可维护性和异常安全性,是编写高质量C++代码的关键技术之一。