智能指针概述

什么是智能指针

智能指针是C++ RAII(Resource Acquisition Is Initialization)思想的典型应用,它们在构造时获取资源,在析构时自动释放资源,从而避免内存泄漏。

主要类型

  • unique_ptr:独占所有权的智能指针

  • shared_ptr:共享所有权的智能指针

  • weak_ptr:不共享所有权的观察指针

原始指针的问题

常见内存问题

cpp

// 问题1: 内存泄漏
void memory_leak() {
    int* ptr = new int(42);
    // 忘记 delete ptr;
}

// 问题2: 重复释放
void double_delete() {
    int* ptr = new int(42);
    delete ptr;
    delete ptr;  // 未定义行为

}

// 问题3: 野指针
void dangling_pointer() {
    int* ptr = new int(42);
    delete ptr;
    *ptr = 100;  // 未定义行为
}

unique_ptr详解

基本用法

cpp

#include <memory>
#include <iostream>

void unique_ptr_basic() {
    // 创建 unique_ptr
    std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
    std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
    
    // 访问数据
    std::cout << *ptr1 << std::endl;  // 42
    arr[0] = 1;
    
    // 释放所有权
    int* raw_ptr = ptr1.release();
    delete raw_ptr;
    
    // 重置指针
    ptr1.reset(new int(100));
    
    // 自动释放内存
} // ptr1 和 arr 自动释放

独占所有权特性

cpp

void ownership_demo() {
    std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
    
    // 错误: 不能复制
    // std::unique_ptr<int> ptr2 = ptr1;
    
    // 正确: 转移所有权
    std::unique_ptr<int> ptr2 = std::move(ptr1);
    
    std::cout << "ptr1: " << (ptr1 ? "not null" : "null") << std::endl;
    std::cout << "ptr2: " << *ptr2 << std::endl;
}

自定义删除器

cpp

struct FileDeleter {
    void operator()(FILE* file) {
        if (file) {
            fclose(file);
            std::cout << "File closed" << std::endl;

        }
    }
};

void custom_deleter_demo() {
    std::unique_ptr<FILE, FileDeleter> filePtr(fopen("test.txt", "w"));
    if (filePtr) {
        fputs("Hello World", filePtr.get());
    }
    // 文件自动关闭
}

shared_ptr详解

基本用法

cpp

void shared_ptr_basic() {
    // 创建 shared_ptr
    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    std::shared_ptr<int> ptr2 = ptr1;  // 共享所有权
    
    std::cout << "Value: " << *ptr1 << std::endl;
    std::cout << "Use count: " << ptr1.use_count() << std::endl;  // 2
    
    ptr1.reset();  // 释放一个引用
    std::cout << "Use count after reset: " << ptr2.use_count() << std::endl;  // 1
}

引用计数机制

cpp

class Resource {
public:
    Resource() { std::cout << "Resource created" << std::endl; }
    ~Resource() { std::cout << "Resource destroyed" << std::endl; }
    void doSomething() { std::cout << "Doing something" << std::endl; }

};

void reference_counting_demo() {
    std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>();
    {
        std::shared_ptr<Resource> ptr2 = ptr1;
        std::cout << "Use count inside block: " << ptr1.use_count() << std::endl;  // 2
    }
    std::cout << "Use count outside block: " << ptr1.use_count() << std::endl;  // 1
    // Resource 在 ptr1 析构时销毁
}

循环引用问题

cpp

struct Node {
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;
    std::string name;
    
    Node(const std::string& n) : name(n) {
        std::cout << "Node " << name << " created" << std::endl;
    }
    
    ~Node() {
        std::cout << "Node " << name << " destroyed" << std::endl;
    }
};

void circular_reference_demo() {
    auto node1 = std::make_shared<Node>("A");
    auto node2 = std::make_shared<Node>("B");
    
    // 创建循环引用
    node1->next = node2;
    node2->prev = node1;
    
    // node1 和 node2 都不会被销毁!
    // 因为引用计数永远不会降到0
}

weak_ptr详解

解决循环引用

cpp

struct SafeNode {
    std::shared_ptr<SafeNode> next;
    std::weak_ptr<SafeNode> prev;  // 使用 weak_ptr 打破循环引用
    std::string name;
    
    SafeNode(const std::string& n) : name(n) {
        std::cout << "SafeNode " << name << " created" << std::endl;
    }
    
    ~SafeNode() {
        std::cout << "SafeNode " << name << " destroyed" << std::endl;

    }
};

void weak_ptr_solution() {
    auto node1 = std::make_shared<SafeNode>("A");
    auto node2 = std::make_shared<SafeNode>("B");
    
    node1->next = node2;
    node2->prev = node1;  // weak_ptr 不会增加引用计数
    
    // 现在 node1 和 node2 可以被正确销毁
}

weak_ptr 使用方法

cpp

void weak_ptr_usage() {
    std::shared_ptr<int> shared = std::make_shared<int>(42);
    std::weak_ptr<int> weak = shared;
    
    // 检查对象是否还存在
    if (auto locked = weak.lock()) {
        std::cout << "Value: " << *locked << std::endl;
        std::cout << "Use count: " << shared.use_count() << std::endl;  // 2
    }
    
    shared.reset();
    
    if (weak.expired()) {
        std::cout << "Object has been destroyed" << std::endl;
    }
}

智能指针最佳实践

1. 优先使用 make_shared 和 make_unique

cpp

// 推荐
auto ptr1 = std::make_unique<int>(42);
auto ptr2 = std::make_shared<std::string>("Hello");

// 不推荐
std::unique_ptr<int> ptr3(new int(42));
std::shared_ptr<std::string> ptr4(new std::string("Hello"));

2. 明确所有权语义

cpp

// 工厂函数返回 unique_ptr
std::unique_ptr<Resource> createResource() {
    return std::make_unique<Resource>();
}

// 需要共享所有权时转换为 shared_ptr
void useSharedResource() {
    auto resource = createResource();
    std::shared_ptr<Resource> sharedResource = std::move(resource);
}

3. 避免原始指针和智能指针混用

cpp

void avoid_mixing() {
    // 危险做法
    int* rawPtr = new int(42);
    std::shared_ptr<int> smartPtr(rawPtr);
    
    // 安全做法
    auto smartPtr = std::make_shared<int>(42);
}

4. 使用 weak_ptr 观察 shared_ptr

cpp

class Observer {
    std::weak_ptr<Resource> resource_;
    
public:
    void setResource(std::shared_ptr<Resource> resource) {
        resource_ = resource;
    }
    
    void useResource() {
        if (auto resource = resource_.lock()) {
            resource->doSomething();
        } else {
            std::cout << "Resource no longer available" << std::endl;
        }
    }
};

实战示例

示例1:资源管理类

cpp

class DatabaseConnection {
private:
    std::unique_ptr<sqlite3, decltype(&sqlite3_close)> db_;
    
public:
    DatabaseConnection(const std::string& filename) 
        : db_(nullptr, sqlite3_close) {
        sqlite3* db;
        if (sqlite3_open(filename.c_str(), &db) == SQLITE_OK) {
            db_.reset(db);
        }
    }
    
    bool isValid() const { return db_ != nullptr; }
    sqlite3* get() const { return db_.get(); }
    
    // 自动关闭数据库连接
};

示例2:对象池模式

cpp

template<typename T>
class ObjectPool {
private:
    std::vector<std::shared_ptr<T>> pool_;
    std::weak_ptr<T> createObject() {
        auto obj = std::make_shared<T>();
        pool_.push_back(obj);
        return obj;
    }
    
public:
    std::shared_ptr<T> acquire() {
        // 清理已销毁的对象
        pool_.erase(
            std::remove_if(pool_.begin(), pool_.end(),
                [](const std::shared_ptr<T>& ptr) { return ptr.use_count() == 1; }),
            pool_.end()
        );
        
        return createObject().lock();
    }
    
    size_t size() const { return pool_.size(); }
};

示例3:缓存系统

cpp

template<typename Key, typename Value>
class Cache {
private:
    std::unordered_map<Key, std::weak_ptr<Value>> cache_;
    mutable std::mutex mutex_;
    
public:
    std::shared_ptr<Value> get(const Key& key) {
        std::lock_guard<std::mutex> lock(mutex_);
        
        auto it = cache_.find(key);
        if (it != cache_.end()) {
            if (auto value = it->second.lock()) {
                return value;  // 缓存命中
            } else {
                cache_.erase(it);  // 清理过期条目
            }
        }
        
        return nullptr;  // 缓存未命中
    }
    
    void set(const Key& key, std::shared_ptr<Value> value) {
        std::lock_guard<std::mutex> lock(mutex_);
        cache_[key] = value;
    }
};

示例4:多线程安全使用

cpp

class ThreadSafeCounter {
private:
    std::shared_ptr<int> counter_ = std::make_shared<int>(0);
    std::mutex mutex_;
    
public:
    void increment() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++(*counter_);
    }
    
    std::shared_ptr<int> getCounter() {
        std::lock_guard<std::mutex> lock(mutex_);
        return counter_;
    }
    
    int getValue() {
        std::lock_guard<std::mutex> lock(mutex_);
        return *counter_;
    }
};

总结

通过合理使用智能指针,你可以:

  1. 彻底告别内存泄漏 - 资源自动管理

  2. 提高代码安全性 - 避免悬空指针和重复释放

  3. 明确所有权语义 - 让代码意图更清晰

  4. 简化异常安全 - 即使在异常情况下资源也能正确释放

  5. 提高开发效率 - 减少手动内存管理的工作量

记住黄金法则:能用智能指针就绝不用原始指针,让C++的内存管理变得简单而安全!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐