C++手写智能指针:从原理到实践

138 阅读4分钟

1. 智能指针的核心思想

  • 共享指针:多个指针共享同一对象,通过引用计数自动释放内存
  • 独占指针:单一所有权,禁止拷贝,支持移动语义

2. 共享指针(SharedPtr)实现剖析

引用计数类:线程安全的计数管理

class SharedCount {
public:
    SharedCount() : count_{1} {}  // 初始值为1
    void add() { ++count_; }      // 原子操作增加计数
    void minus() { --count_; }    // 原子操作减少计数
    int get() const { return count_; }
private:
    std::atomic<int> count_;  // 原子类型保证线程安全
};

关键操作实现

template <typename T>
class SharedPtr {
public: 
    // 构造函数
    SharedPtr() : ptr_(nullptr), ref_count_(new SharedCount){}
    SharedPtr(T* ptr) : ptr_(ptr), ref_count_(new SharedCount){}
    // 拷贝构造:共享计数
    SharedPtr(const SharedPtr& p) {
        this->ptr_ = p.ptr_;
        this->ref_count_ = p.ref_count_;
        ref_count_->add(); // 引用计数+1
        //std::cout << "拷贝构造!" << std::endl;
    }
    // 赋值操作符:处理自我赋值
    SharedPtr& operator=(const SharedPtr& p) {
        if (this != &p) { // 防止自我赋值
            clean();      // 清理当前资源
            this->ptr_ = p.ptr_;
            this->ref_count_ = p.ref_count_;
            ref_count_->add();
            //std::cout << "赋值!" << std::endl;
        }
        return *this;
    }
    // 析构:计数归零时释放资源
    ~SharedPtr() {
        clean();
    }


    int use_count() {
        return ref_count_->get();
    }
    T* get() const {
        return ptr_;
    }
    T* operator->() const {
        return ptr_;
    }
    T& operator*() const {
        return *ptr_;
    }
    operator bool() const {
        return ptr_;
    }

private:
    void clean() {
        if (ref_count_) {
            ref_count_->minus();
            if (ref_count_->get() == 0) { // 无引用时释放
                if (ptr_) delete ptr_;
                delete ref_count_;
            }
        }
    }
private:
    T* ptr_;
    SharedCount* ref_count_;
};

3. 独占指针(UniquePtr)实现要点

禁用拷贝语义

UniquePtr(const UniquePtr& p) = delete;             // 禁用拷贝构造
UniquePtr& operator=(const UniquePtr& p) = delete;  // 禁用拷贝赋值

移动语义支持

// 移动构造:转移所有权
UniquePtr(UniquePtr&& p) noexcept {
    ptr_ = p.ptr_;
    p.ptr_ = nullptr;  // 置空原指针
}

// 移动赋值:先释放现有资源
UniquePtr& operator=(UniquePtr&& p) noexcept {
    if (this != &p) {  // 处理自我移动
        clean();       // 释放当前资源
        ptr_ = p.ptr_;
        p.ptr_ = nullptr;
    }
    return *this;
}

4. 完整代码及测试分析

#include <iostream>
#include <atomic>

class SharedCount {
public:
    SharedCount() : count_{1}{}
    void add() {
        ++count_;
    }
    void minus() {
        --count_;
    }
    int get() const {
        return count_;
    }

private:
    std::atomic<int> count_;
};

template <typename T>
class SharedPtr {
public: 
    // 构造函数
    SharedPtr() : ptr_(nullptr), ref_count_(new SharedCount){}
    SharedPtr(T* ptr) : ptr_(ptr), ref_count_(new SharedCount){}
    // 拷贝构造:共享计数
    SharedPtr(const SharedPtr& p) {
        this->ptr_ = p.ptr_;
        this->ref_count_ = p.ref_count_;
        ref_count_->add(); // 引用计数+1
        //std::cout << "拷贝构造!" << std::endl;
    }
    // 赋值操作符:处理自我赋值
    SharedPtr& operator=(const SharedPtr& p) {
        if (this != &p) { // 防止自我赋值
            clean();      // 清理当前资源
            this->ptr_ = p.ptr_;
            this->ref_count_ = p.ref_count_;
            ref_count_->add();
            //std::cout << "赋值!" << std::endl;
        }
        return *this;
    }
    // 析构:计数归零时释放资源
    ~SharedPtr() {
        clean();
    }


    int use_count() {
        return ref_count_->get();
    }
    T* get() const {
        return ptr_;
    }
    T* operator->() const {
        return ptr_;
    }
    T& operator*() const {
        return *ptr_;
    }
    operator bool() const {
        return ptr_;
    }

private:
    void clean() {
        if (ref_count_) {
            ref_count_->minus();
            if (ref_count_->get() == 0) { // 无引用时释放
                if (ptr_) delete ptr_;
                delete ref_count_;
            }
        }
    }
private:
    T* ptr_;
    SharedCount* ref_count_;
};

template <typename T>
class UniquePtr {
public:
    UniquePtr() : ptr_(nullptr) {}
    UniquePtr(T* ptr) : ptr_(ptr){}
    UniquePtr(const UniquePtr& p) = delete; // 禁用拷贝构造
    UniquePtr& operator=(const UniquePtr& p) = delete; // 禁用拷贝赋值
    UniquePtr(UniquePtr&& p) noexcept {
            this->ptr_ = p.ptr_;
            p.ptr_ = nullptr; // 置空原指针
            std::cout << "移动构造!" << std::endl;
    }
    UniquePtr& operator=(UniquePtr&& p) noexcept {
        if (this != &p) { // 处理自我赋值
            clean(); // 释放当前资源
            this->ptr_ = p.ptr_;
            p.ptr_ = nullptr;
        }
        std::cout << "移动赋值!" << std::endl;
        return *this;
    }

    T* get() const {
        return ptr_;
    }
    T* operator->() const {
        return ptr_;
    }
    T& operator*() const {
        return *ptr_;
    }
    operator bool() const {
        return ptr_;
    }
    
    ~UniquePtr() {
        clean();
    }

private:
    void clean() {
        if (ptr_) delete ptr_;
    }
    T* ptr_;
};


class A {
public:
    A() {
        std::cout << "A()" << std::endl;
    }
    ~A() {
        std::cout << "~A()" << std::endl;
    }
};

int main() {
 // SharedPtr测试
    SharedPtr<A> a(new A);  // 输出:A()
    {
        SharedPtr<A> a2;
        a2 = a;  // 调用赋值操作符,计数+1
        cout << a.use_count() << endl;  // 输出:2
    }  // a2析构,计数-1
    cout << a.use_count() << endl;  // 输出:1

    // UniquePtr测试
    UniquePtr<A> b(new A);  // 输出:A()
    UniquePtr<A> b2(std::move(b));  // 移动构造,b失效
    // b2离开作用域时输出:~A()
    return 0;
}

5. 关键实现细节

  1. 线程安全std::atomic确保引用计数原子操作
  2. 自我赋值处理operator=中检查this != &p
  3. 移动语义
    • 移动后置空原指针,避免双重释放
    • 使用noexcept声明不抛出异常
  4. 资源释放
    • SharedPtr在计数归零时删除对象和计数器
    • UniquePtr在析构时直接释放资源

6. 总结

  • SharedPtr适用于共享所有权场景,通过引用计数自动管理生命周期
  • UniquePtr轻量高效,适用于独占资源,支持移动语义
  • 手写实现需注意线程安全、自我赋值和资源释放边界条件

通过实现智能指针,可深入理解RAII(资源获取即初始化)原则,提升内存管理能力。实际开发中推荐使用std::shared_ptrstd::unique_ptr,它们经过充分优化并严格遵循C++标准。