C++ 智能指针详解与代码示例

智能指针是 C++ 中用于自动管理动态内存 的工具,核心思想是利用栈对象的生命周期管理(RAII),在智能指针对象离开作用域时自动释放所管理的堆内存,从而避免内存泄漏。

C++11 标准库提供了三种主要的智能指针,它们定义在 头文件中:

智能指针类型 头文件 核心特点 所有权模型
std::unique_ptr <memory> 独占对象所有权,不可复制,只能移动 独占所有权
std::shared_ptr <memory> 共享对象所有权,通过引用计数管理 共享所有权
std::weak_ptr <memory> 辅助型,不控制对象生命周期,可解决循环引用问题 无所有权
一、 std::unique_ptr ------ 独占所有权

unique_ptr 代表对一个对象的独占所有权。这意味着在任何时候,只有一个 unique_ptr 可以指向同一个对象。当这个 unique_ptr 被销毁(离开作用域、被重置或被移动)时,它所指向的对象会被自动释放。

核心特性

  • 不可复制:禁用了拷贝构造函数和拷贝赋值运算符。
  • 可移动:可以通过 std::move 将所有权从一个 unique_ptr 转移到另一个。
  • 高效:几乎没有额外开销,大小通常与裸指针相同。

代码示例

cpp 复制代码
  
#include <iostream>
#include <memory> // 包含智能指针头文件

class Device {
public:
    std::string name;
    Device(const std::string& n) : name(n) {
        std::cout << "Device " << name << " 已创建\n";
    }
    ~Device() {
        std::cout << "Device " << name << " 已销毁\n";
    }
    void run() {
        std::cout << "Device " << name << " 正在运行...\n";
    }
};

int main() {
    // 1. 创建 unique_ptr,管理一个 Device 对象
    std::unique_ptr<Device> dev1 = std::make_unique<Device>("CNC_01");
    
    // 使用 -> 访问对象成员
    dev1->run();
    
    // 2. 尝试复制(编译错误)
    // std::unique_ptr<Device> dev2 = dev1; // 错误:unique_ptr 不可复制
    
    // 3. 移动所有权(正确)
    std::unique_ptr<Device> dev2 = std::move(dev1);
    if (!dev1) {
        std::cout << "dev1 现在为空,所有权已转移给 dev2\n";
    }
    dev2->run();
    
    // 4. 手动释放资源(可选,通常不需要)
    dev2.reset(); // 此时 Device 对象被销毁
    if (!dev2) {
        std::cout << "dev2 现在为空\n";
    }

    // 注意:当 dev1 和 dev2 离开 main 作用域时,如果它们还持有对象,会自动释放
    return 0;
}
 

输出

plaintext

Device CNC_01 已创建

Device CNC_01 正在运行...

dev1 现在为空,所有权已转移给 dev2

Device CNC_01 正在运行...

Device CNC_01 已销毁

dev2 现在为空

二、 std::shared_ptr ------ 共享所有权

shared_ptr 允许多个指针共享同一个对象的所有权 。它内部维护一个线程安全的引用计数。当最后一个指向该对象的 shared_ptr 被销毁时,对象才会被释放。

核心特性

  • 可复制:拷贝时引用计数加1。
  • 可移动:移动时引用计数不变。
  • 线程安全:引用计数的增减是原子操作。

代码示例

cpp 复制代码
  
#include <iostream>
#include <memory>
#include <vector>

int main() {
    // 1. 创建 shared_ptr,管理一个 Device 对象
    std::shared_ptr<Device> dev1 = std::make_shared<Device>("Robot_Arm");
    std::cout << "当前引用计数: " << dev1.use_count() << "\n"; // 输出 1

    // 2. 复制 shared_ptr,引用计数增加
    std::shared_ptr<Device> dev2 = dev1;
    std::cout << "当前引用计数: " << dev1.use_count() << "\n"; // 输出 2

    // 3. 将 shared_ptr 放入容器
    std::vector<std::shared_ptr<Device>> deviceList;
    deviceList.push_back(dev1);
    deviceList.push_back(dev2);
    std::cout << "当前引用计数: " << dev1.use_count() << "\n"; // 输出 4

    // 4. 销毁一个 shared_ptr,引用计数减少
    dev1.reset();
    std::cout << "当前引用计数: " << dev2.use_count() << "\n"; // 输出 3

    // 5. 清空容器,引用计数继续减少
    deviceList.clear();
    std::cout << "当前引用计数: " << dev2.use_count() << "\n"; // 输出 1

    // 6. 当最后一个 shared_ptr (dev2) 离开作用域时,对象被销毁
    return 0;
}
 

输出

plaintext

Device Robot_Arm 已创建

当前引用计数: 1

当前引用计数: 2

当前引用计数: 4

当前引用计数: 3

当前引用计数: 1

Device Robot_Arm 已销毁

三、 std::weak_ptr ------ 解决循环引用

weak_ptr 是为了配合 shared_ptr 而设计的。它不拥有对象的所有权 ,因此不会增加或减少引用计数。它的主要作用是观察由 shared_ptr 管理的对象,而不影响其生命周期,从而解决 shared_ptr 的循环引用问题。

循环引用问题

当两个对象互相持有对方的 shared_ptr 时,它们的引用计数永远不会降为0,导致内存泄漏。

cpp 复制代码
  
// ❌ 错误示例:循环引用导致内存泄漏
class Node {
public:
    std::shared_ptr<Node> next;
    ~Node() { std::cout << "Node 已销毁\n"; }
};

int main() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();
    node1->next = node2; // node1 持有 node2
    node2->next = node1; // node2 持有 node1
    // 离开作用域时,两个 Node 对象都不会被销毁!
}
 
 
使用  weak_ptr  解决问题
 
cpp
  
#include <iostream>
#include <memory>

// ✅ 正确示例:使用 weak_ptr 打破循环
class Node {
public:
    std::weak_ptr<Node> next; // 使用 weak_ptr 替代 shared_ptr
    ~Node() { std::cout << "Node 已销毁\n"; }
};

int main() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();
    
    node1->next = node2; // weak_ptr 不增加引用计数
    node2->next = node1;

    // 离开作用域时,node1 和 node2 被销毁,它们管理的 Node 对象也被销毁
    return 0;
}

输出

plaintext

Node 已销毁

Node 已销毁

如何使用 weak_ptr

由于 weak_ptr 不拥有对象,不能直接使用 * 或 -> 操作符。必须先将其提升为 shared_ptr ,并检查对象是否还存在。

cpp 复制代码
  
int main() {
    auto shared = std::make_shared<Device>("Sensor");
    std::weak_ptr<Device> weak = shared;

    // 提升为 shared_ptr 以访问对象
    if (auto locked = weak.lock()) {
        locked->run(); // 安全访问
        std::cout << "对象仍然存在\n";
    } else {
        std::cout << "对象已被销毁\n";
    }

    shared.reset(); // 销毁对象

    // 再次尝试提升
    if (auto locked = weak.lock()) {
         locked->run();
     } else {
         std::cout << "对象已被销毁,无法访问\n";
     }
     return 0;
 }
四、最佳实践与总结

1. 优先使用 std::make_unique 和 std::make_shared

  • 它们能保证异常安全,避免内存泄漏。
  • make_shared 还能优化内存分配,将控制块和对象内存分配在一起。
cpp 复制代码
 // 推荐
auto dev = std::make_unique<Device>("Machine");
auto dev = std::make_shared<Device>("Machine");

// 不推荐(可能导致内存泄漏)
Device* raw = new Device("Machine");
std::unique_ptr<Device> dev(raw);

2. 避免裸指针与智能指针混用

  • 不要将同一个裸指针交给多个智能指针管理,这会导致重复释放。
  • 一旦对象交给智能指针管理,就不要再使用裸指针操作它。

3. 选择合适的智能指针

  • unique_ptr :当你需要独占对象所有权,且性能要求高时。它是默认选择。
  • shared_ptr :当你需要多个所有者共享对象时。注意其引用计数的开销。
  • weak_ptr :专门用于解决 shared_ptr 的循环引用,或在不拥有所有权的情况下观察对象。
五、 手写 unique_ptr 核心实现
cpp 复制代码
  
#include <iostream>
using namespace std;

template <typename T>
class MyUniquePtr {
private:
    T* _ptr;

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

public:
    // 构造
    explicit MyUniquePtr(T* p = nullptr) : _ptr(p) {}

    // 移动构造
    MyUniquePtr(MyUniquePtr&& other) noexcept {
        _ptr = other._ptr;
        other._ptr = nullptr;
    }

    // 移动赋值
    MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
        if (this != &other) {
            delete _ptr;
            _ptr = other._ptr;
            other._ptr = nullptr;
        }
        return *this;
    }

    // 析构
    ~MyUniquePtr() {
        delete _ptr;
        _ptr = nullptr;
    }

    // 解引用
    T& operator*()  const { return *_ptr; }
    T* operator->() const { return _ptr; }

    T* get() const { return _ptr; }
};

// 测试
struct Test {
    Test()  { cout << "Test 构造\n"; }
    ~Test() { cout << "Test 析构\n"; }
};

int main() {
    MyUniquePtr<Test> p1(new Test);
    MyUniquePtr<Test> p2 = move(p1); // 转移所有权
    return 0;
}

核心原理:

  • 禁止拷贝,只允许移动
  • 析构时直接 delete
  • 无引用计数,效率最高
六、 手写 shared_ptr 核心实现(带引用计数)
cpp 复制代码
  
#include <iostream>
using namespace std;

template <typename T>
class MySharedPtr {
private:
    T* _ptr;
    int* _refCount; // 引用计数必须在堆上,共享

    void release() {
        if (_ptr) {
            if (--(*_refCount) == 0) {
                delete _ptr;
                delete _refCount;
                _ptr = nullptr;
                _refCount = nullptr;
            }
        }
    }

public:
    // 构造
    explicit MySharedPtr(T* p = nullptr) : _ptr(p) {
        _refCount = new int(1);
    }

    // 拷贝构造:计数+1
    MySharedPtr(const MySharedPtr& other) {
        _ptr = other._ptr;
        _refCount = other._refCount;
        (*_refCount)++;
    }

    // 拷贝赋值
    MySharedPtr& operator=(const MySharedPtr& other) {
        if (this != &other) {
            release(); // 释放当前资源

            _ptr = other._ptr;
            _refCount = other._refCount;
            (*_refCount)++;
        }
        return *this;
    }

    // 析构
    ~MySharedPtr() {
        release();
    }

    int use_count() const { return *_refCount; }
    T* get() const { return _ptr; }
    T& operator*()  const { return *_ptr; }
    T* operator->() const { return _ptr; }
};

// 测试
struct Test {
    Test()  { cout << "Test 构造\n"; }
    ~Test() { cout << "Test 析构\n"; }
};

int main() {
    MySharedPtr<Test> p1(new Test);
    MySharedPtr<Test> p2 = p1;
    cout << "count: " << p1.use_count() << endl; // 2
    return 0;
}
 

核心原理:

  • 堆上维护引用计数
  • 拷贝 = 计数+1
  • 析构/赋值 = 计数-1
  • 计数为0才真正释放对象
七、 手写 weak_ptr 核心实现(解决循环引用)

weak_ptr 必须和 shared_ptr 配合,不增加强引用计数

cpp 复制代码
  
#include <iostream>
using namespace std;

// 先声明
template <typename T> class MySharedPtr;

template <typename T>
class MyWeakPtr {
private:
    T* _ptr;
    int* _refCount;
    int* _weakCount;

public:
    // 从 shared_ptr 构造
    MyWeakPtr(const MySharedPtr<T>& other) {
        _ptr = other._ptr;
        _refCount = other._refCount;
        _weakCount = other._weakCount;
        (*_weakCount)++;
    }

    ~MyWeakPtr() {
        (*_weakCount)--;
    }

    // 提升为 shared_ptr
    MySharedPtr<T> lock() const {
        if (*_refCount > 0)
            return MySharedPtr<T>(*this);
        return MySharedPtr<T>();
    }

    bool expired() const { return *_refCount == 0; }
};

// 完整版 MySharedPtr(支持 weak_ptr)
template <typename T>
class MySharedPtr {
private:
    T* _ptr;
    int* _refCount;  // 强引用
    int* _weakCount; // 弱引用

    friend class MyWeakPtr<T>;

    void release() {
        if (_ptr) {
            if (--(*_refCount) == 0) {
                delete _ptr;
                _ptr = nullptr;
                // 弱计数为0才释放控制块
                if (*_weakCount == 0) {
                    delete _refCount;
                    delete _weakCount;
                }
            }
        }
    }

public:
    explicit MySharedPtr(T* p = nullptr) : _ptr(p) {
        _refCount = new int(1);
        _weakCount = new int(0);
    }

    MySharedPtr(const MyWeakPtr<T>& wp) {
        _ptr = wp._ptr;
        _refCount = wp._refCount;
        _weakCount = wp._weakCount;
        (*_refCount)++;
    }

    MySharedPtr(const MySharedPtr& other) {
    ptr = other._ptr;
         _refCount = other._refCount;
         _weakCount = other._weakCount;
         (*_refCount)++;
     }
    MySharedPtr& operator=(const MySharedPtr& other) {
         if (this != &other) {
             release();
             _ptr = other._ptr;
             _refCount = other._refCount;
             _weakCount = other._weakCount;
             (*_refCount)++;
         }
         return *this;
     }
    ~MySharedPtr() {
         release();
     }
     int use_count() const { return *_refCount; }
 };
 // 测试循环引用
 struct A;
 struct B;
 struct A {
     MyWeakPtr<B> b;
     A() { cout << "A 构造\n"; }
     ~A() { cout << "A 析构\n"; }
 };
 struct B {
     MySharedPtr<A> a;
     B() { cout << "B 构造\n"; }
     ~B() { cout << "B 析构\n"; }
 };
 int main() {
     MySharedPtr<A> a(new A);
     MySharedPtr<B> b(new B);
     a->b = b;
     b->a = a;
     return 0;
 }

核心原理:

  • 只增加弱计数,不增加强计数
  • 不影响对象生命周期
  • 用 lock() 安全提升为 shared_ptr
  • 解决循环引用内存泄漏

极简总结(背这个)

  • unique_ptr:禁止拷贝,独占所有权,直接delete。
  • shared_ptr:堆上引用计数,拷贝+1,析构-1,为0释放。
  • weak_ptr:观察者,不增加强计数,解决循环引用。
相关推荐
小杍随笔2 小时前
【Rust模块化进阶:深入解析mod.rs的用法与现代实践(1.94版本)】
开发语言·后端·rust
小鹿软件办公2 小时前
KDE 重磅发布:digiKam 9.0 正式登场,全面升级 Qt 6 核心
开发语言·qt·digikam
Ronin2 小时前
QT中使用toInt函数判断条件时,要注意越界
开发语言·qt
载数而行5202 小时前
QT系列,对象树 栈和堆 QDebug以及日志打印
c++·qt·学习
小二·2 小时前
Go 语言系统编程与云原生开发实战(第35篇)
开发语言·云原生·golang
xiaoye-duck2 小时前
《算法题讲解指南:优选算法-分治-快排》--45.数组中的第k个最大元素,46.最小的k个数
c++·算法
SCBAiotAigc2 小时前
2026.3.7:具身智能之51单片机<二>:ISP烧录过程
c++·人工智能·单片机·嵌入式硬件·51单片机·c
tankeven2 小时前
HJ125 最大最小路
c++·算法
AI-小柒2 小时前
OpenClaw技术深度解析:从智能助手到自动化引擎的范式革命(附DataEyes实战)
大数据·运维·开发语言·人工智能·python·http·自动化