一、简介

C++ 智能指针是 C++11 标准引入的重要特性,用于解决传统 C++ 中手动内存管理带来的各种问题,包括内存泄漏、野指针、重复释放等。智能指针本质上是封装了原始指针的类模板,通过 RAII(Resource Acquisition Is Initialization)技术实现自动内存管理。

主要优势:

  • 自动内存释放:当智能指针离开作用域时,自动释放其管理的内存
  • 所有权明确:清晰表达资源的所有权关系
  • 异常安全:即使发生异常,资源也能被正确释放
  • 线程安全:部分智能指针提供线程安全的引用计数

C++11 提供了三种主要的智能指针:

  • unique_ptr:独占所有权的智能指针
  • shared_ptr:共享所有权的智能指针
  • weak_ptr:不拥有所有权的弱引用指针

二、unique_ptr

unique_ptr 是一种独占所有权的智能指针,同一时间只能有一个 unique_ptr 指向某个对象。当 unique_ptr 被销毁时,它所指向的对象也会被自动释放。

基本特性

  • 独占所有权:不能复制,只能移动
  • 零开销:与原始指针相比几乎没有性能损失
  • 支持自定义删除器

基本用法

#include <memory>
#include <iostream>
using namespace std;

// 创建并返回 unique_ptr
unique_ptr<int> create_unique() {
    // 方式1:使用 new 直接初始化
    unique_ptr<int> uptr(new int(10));
    
    // 方式2:使用 make_unique (C++14 推荐)
    // auto uptr = make_unique<int>(10);
    
    cout << "函数内值: " << *uptr << endl;
    return uptr; // 通过移动语义返回
}

int main() {
    // 接收返回的 unique_ptr
    unique_ptr<int> uptr = create_unique();
    
    // 访问对象
    cout << "main 中值: " << *uptr << endl;
    
    // 获取原始指针(但不应该手动释放)
    int* raw_ptr = uptr.get();
    cout << "原始指针值: " << *raw_ptr << endl;
    
    // 释放所有权(返回原始指针并释放管理权)
    int* released_ptr = uptr.release();
    delete released_ptr; // 需要手动释放
    
    // 检查是否为空
    if (!uptr) {
        cout << "uptr 已释放所有权" << endl;
    }
    
    return 0;
}

运行结果

函数内值: 10
main 中值: 10
原始指针值: 10
uptr 已释放所有权

注意事项

  • 不能复制,只能移动:unique_ptr 删除了拷贝构造函数和拷贝赋值运算符
  • 可以使用 std::move() 转移所有权
  • 优先使用 make_unique (C++14) 而不是直接 new,更安全高效
  • get() 只是临时借用地址,所有权仍归智能指针
  • release() 是彻底移交所有权,之后必须手动释放内存

三、move

由于 unique_ptr 独占所有权的特性,不能直接复制,只能通过 std::move() 转移所有权。

#include <memory>
#include <iostream>
using namespace std;

int main() {
    // 创建 unique_ptr
    unique_ptr<int> uptr1(new int(42));
    
    cout << "uptr1 值: " << *uptr1 << endl;
    cout << "uptr1 地址: " << uptr1.get() << endl;
    
    // 转移所有权(使用 std::move)
    unique_ptr<int> uptr2 = move(uptr1);
    
    cout << "转移后:" << endl;
    cout << "uptr1 地址: " << uptr1.get() << endl; // 变为 nullptr
    cout << "uptr2 值: " << *uptr2 << endl;
    cout << "uptr2 地址: " << uptr2.get() << endl; // 与原 uptr1 相同
    
    // 检查 uptr1 是否为空
    if (!uptr1) {
        cout << "uptr1 已变为空" << endl;
    }
    
    return 0;
}

运行结果

uptr1 值: 42
uptr1 地址: 0x55a95eb762b0
转移后:
uptr1 地址: 0
uptr2 值: 42
uptr2 地址: 0x55a95eb762b0
uptr1 已变为空

重要原则

  1. 移动后源指针变为 nullptr,不再拥有对象所有权
  2. 确保不会同时有多个 unique_ptr 管理同一对象
  3. 在函数间传递时,优先使用移动语义而非原始指针

四、share_ptr

shared_ptr 实现了共享所有权的智能指针,多个 shared_ptr 可以指向同一个对象,通过引用计数机制管理内存。当最后一个 shared_ptr 被销毁时,对象才会被释放。

引用计数机制

  • 每个 shared_ptr 都会增加引用计数
  • 拷贝赋值会增加引用计数
  • 析构或重置会减少引用计数
  • 当引用计数归零时,自动删除管理对象

基本用法

#include <memory>
#include <iostream>
using namespace std;

// 创建并返回 shared_ptr
shared_ptr<int> create_shared() {
    // 方式1:使用 new 直接初始化
    shared_ptr<int> sptr(new int(10));
    
    // 方式2:使用 make_shared (推荐,更高效)
    // auto sptr = make_shared<int>(10);
    
    cout << "函数内值: " << *sptr << endl;
    cout << "函数内引用计数: " << sptr.use_count() << endl;
    return sptr;
}

int main() {
    // 创建 shared_ptr
    shared_ptr<int> sptr1 = create_shared();
    cout << "sptr1 引用计数: " << sptr1.use_count() << endl;
    
    // 共享所有权(拷贝构造)
    shared_ptr<int> sptr2 = sptr1;
    cout << "创建 sptr2 后引用计数: " << sptr1.use_count() << endl;
    
    // 再次共享
    shared_ptr<int> sptr3 = sptr2;
    cout << "创建 sptr3 后引用计数: " << sptr1.use_count() << endl;
    
    // 获取原始指针
    int* raw_ptr = sptr1.get();
    cout << "原始指针值: " << *raw_ptr << endl;
    
    // 释放一个 shared_ptr
    sptr1.reset();
    cout << "释放 sptr1 后引用计数: " << sptr2.use_count() << endl;
    
    // 再次释放
    sptr2.reset();
    cout << "释放 sptr2 后引用计数: " << sptr3.use_count() << endl;
    
    // 最后一个 shared_ptr 释放时,对象自动销毁
    sptr3.reset();
    cout << "释放 sptr3 后引用计数: 0" << endl;
    
    // 注意:此时 raw_ptr 已成为悬空指针,不应再使用
    // cout << *raw_ptr << endl; // 危险行为!
    
    return 0;
}

运行效果

函数内值: 10
函数内引用计数: 1
sptr1 引用计数: 1
创建 sptr2 后引用计数: 2
创建 sptr3 后引用计数: 3
原始指针值: 10
释放 sptr1 后引用计数: 2
释放 sptr2 后引用计数: 1
释放 sptr3 后引用计数: 0

循环引用问题

shared_ptr 可能存在循环引用问题,导致内存泄漏:

#include <memory>
#include <iostream>
using namespace std;

struct Node {
    shared_ptr<Node> next;
    shared_ptr<Node> prev;
    int value;
    
    Node(int val) : value(val) {
        cout << "Node " << val << " 创建" << endl;
    }
    
    ~Node() {
        cout << "Node " << value << " 销毁" << endl;
    }
};

int main() {
    // 创建循环引用
    shared_ptr<Node> node1 = make_shared<Node>(1);
    shared_ptr<Node> node2 = make_shared<Node>(2);
    
    node1->next = node2; // node2的引用计数变为2(node2本身和node1->next都指向它)
    node2->prev = node1; // node1的引用计数变为2(node1本身和node2->prev都指向它)
    
    // node1离开作用域,引用计数从 2 减为 1(不是 0)
    // node2离开作用域,引用计数从 2 减为 1(不是 0)

    // 即使离开作用域,引用计数也不会归零,导致内存泄漏
    return 0;
}

注意事项

  • 避免循环引用,这会导致引用计数无法归零,造成内存泄漏:
  • 可以使用 std::make_shared 更安全高效地创建 shared_ptr;

五、make_shared

简介

std::make_shared 是 C++11 引入的模板函数,定义在 <memory> 头文件中,用于创建 std::shared_ptr 智能指针。它是推荐创建 shared_ptr 的方式,相比直接使用 shared_ptr 构造函数有显著优势。

基本用法

#include <memory>
#include <iostream>
using namespace std;

// 尾置返回类型
// 完全等价 shared_ptr<int> func()
auto func() -> shared_ptr<int> {
    // 使用 make_shared 动态分配内存
    auto sptr = make_shared<int>(10); 
    
    cout << "函数中: " << *sptr << endl;
    return sptr; // 返回 shared_ptr
}

int main() {
    // 接收返回的 shared_ptr
    shared_ptr<int> sptr1 = func();
    
    // 共享所有权
    shared_ptr<int> sptr2 = sptr1;
    
    // 获取原始指针
    int* ptr = sptr1.get();
    
    // 输出信息
    cout << "*sptr1: " << *sptr1 << endl;
    cout << "*sptr2: " << *sptr2 << endl;
    
    // 释放一个 shared_ptr
    sptr1 = nullptr;
    cout << "sptr1释放后,*ptr: " << *ptr << endl; // 此时对象仍存在
    
    // 释放最后一个 shared_ptr
    sptr2 = nullptr;
    cout << "sptr2释放后,*ptr: " << *ptr << endl; // 危险!对象已释放
    
    return 0;
}

与自定义类结合使用

#include <memory>
#include <iostream>
using namespace std;

class MyClass {
public:
    MyClass(int value) : data(value) {
        cout << "MyClass 构造函数" << endl;
    }
    
    ~MyClass() {
        cout << "MyClass 析构函数" << endl;
    }
    
    void print() {
        cout << "数据: " << data << endl;
    }

private:
    int data;
};

int main() {
    // 使用 make_shared 创建 shared_ptr,传递构造函数参数
    auto ptr = make_shared<MyClass>(42);
    
    // 调用对象方法
    ptr->print();
    
    // 不需要手动释放内存,shared_ptr 会自动管理
    return 0;
}

优势

  1. 效率更高make_shared 会一次性分配对象内存和引用计数所需的内存,而直接使用 shared_ptr 构造函数需要两次内存分配(一次给对象,一次给引用计数)。
  2. 异常安全:避免了在构造函数抛出异常时可能导致的内存泄漏。
  3. 代码更简洁
    • 不需要显式指定模板参数类型(可通过自动类型推导)
    • 不需要使用 new 操作符
    • 减少代码重复

注意事项

  • make_shared 不支持自定义删除器,如需自定义删除器,仍需使用 shared_ptr 构造函数
  • 对象内存可能延迟释放:由于对象和引用计数在同一内存块中,即使对象引用计数归零,只要还有 weak_ptr 存在,内存就不会被释放

六、weak_ptr

简介

weak_ptr 是一种弱引用智能指针,它不拥有对象的所有权,主要用于解决 shared_ptr 的循环引用问题。它必须从一个 shared_ptr 或另一个 weak_ptr 构造

循环引用问题

循环引用是使用 shared_ptr 时最常见的陷阱之一,会导致内存泄漏。当两个或多个 shared_ptr 相互引用形成闭环时,它们的引用计数永远无法减为 0,从而导致指向的对象无法被释放。

  • 示例:循环引用导致内存泄漏
#include <memory>
#include <iostream>
using namespace std;

class B; // 前向声明

class A {
public:
    shared_ptr<B> b_ptr; // A 持有 B 的 shared_ptr
    ~A() { cout << "A 被销毁" << endl; }
};

class B {
public:
    shared_ptr<A> a_ptr; // B 持有 A 的 shared_ptr
    ~B() { cout << "B 被销毁" << endl; }
};

int main() {
    auto a = make_shared<A>();
    auto b = make_shared<B>();
    
    a->b_ptr = b; // a 引用 b
    b->a_ptr = a; // b 引用 a,形成循环引用
    
    return 0;
    // 离开作用域时,a 和 b 的引用计数仍为 1,不会被销毁
    // A 和 B 的内存被泄漏,析构函数不会被调用
}

问题分析:

当 a 和 b 离开作用域时,它们的引用计数各减 1(从 2 变为 1)。由于 a->b_ptr 仍引用 b,b->a_ptr 仍引用 a,引用计数无法归零,最终导致内存泄漏。

使用 weak_ptr 解决循环引用

将其中一个 shared_ptr 改为 weak_ptr 即可打破循环引用:

#include <memory>
#include <iostream>
using namespace std;

class B; // 前向声明

class A {
public:
    shared_ptr<B> b_ptr; // A 持有 B 的 shared_ptr
    ~A() { cout << "A 被销毁" << endl; }
};

class B {
public:
    weak_ptr<A> a_ptr; // 将 shared_ptr 改为 weak_ptr
    ~B() { cout << "B 被销毁" << endl; }
};

int main() {
    auto a = make_shared<A>();
    auto b = make_shared<B>();
    
    a->b_ptr = b; // a 引用 b
    b->a_ptr = a; // b 弱引用 a,不增加引用计数
    
    cout << "程序结束" << endl;
    return 0;
    
    // 离开作用域时:
    // 1. a 的引用计数从 1 减为 0 → A 被销毁
    // 2. A 销毁后,b_ptr 被销毁,b 的引用计数从 2 减为 1
    // 3. b 的引用计数从 1 减为 0 → B 被销毁
}

weak_ptr 的常用操作

  • lock(): 尝试获取一个指向对象的 shared_ptr,如果对象已被释放,则返回空的 shared_ptr
  • expired(): 检查对象是否已被释放
  • use_count(): 返回与 weak_ptr 共享对象的 shared_ptr 的数量
  • 示例:使用 lock() 安全访问对象
#include <memory>
#include <iostream>
using namespace std;

int main() {
    shared_ptr<int> sptr = make_shared<int>(42);
    weak_ptr<int> wptr = sptr;
    
    // 使用 lock() 获取 shared_ptr
    if (auto shared = wptr.lock()) {
        cout << "值: " << *shared << endl; // 输出 42
    }
    
    sptr.reset(); // 释放对象
    
    if (auto shared = wptr.lock()) {
        cout << "值: " << *shared << endl; // 不会执行
    } else {
        cout << "对象已释放" << endl; // 输出此内容
    }
    
    return 0;
}
  • wptr.lock() 是弱引用访问对象的唯一安全方式
    • 如果对象还存在(未被释放),返回一个指向该对象的 shared_ptr(此时引用计数会临时加 1)
    • 如果对象已被释放,返回一个空的 shared_ptr
  • 这里对象还存在(sptr 还在管理它),所以 shared 是有效的,会输出 42

常见使用场景

  1. 双向链表:节点的 prev 或 next 指针使用 weak_ptr
  2. 观察者模式:观察者对被观察者的引用使用 weak_ptr,避免影响被观察者的生命周期
  3. 缓存场景:缓存对象对源对象的引用使用 weak_ptr,避免源对象无法释放

建议

  • 当两个对象需要互相引用时,至少一方应使用 weak_ptr,以防止循环引用导致的内存泄漏
  • 使用 weak_ptr 访问对象前,总是先使用 lock() 方法获取 shared_ptr,并检查其有效性
  • 优先使用 make_shared 创建 shared_ptr,然后再创建相关的 weak_ptr
Logo

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

更多推荐