在 C++ 中,内存管理一直是一个核心且容易出错的问题。传统的原始指针(raw pointer)需要程序员手动管理其生命周期,一旦处理不当,就容易导致内存泄漏、悬空指针、重复释放等问题。为了解决这些问题,C++ 引入了更加安全和现代化的工具——智能指针(Smart Pointer)。

1. 基本概念

1.1. 智能指针的基本概念

        智能指针是一种抽象的数据类型,它通常通过类模板实现,从而具备良好的泛型能力,可以用于管理任意类型的动态资源。与普通指针不同,智能指针并不仅仅是一个地址的持有者,它本质上是一个“对象”,内部封装了原始指针,并通过类的析构函数自动释放指针所指向的内存或对象。将“资源释放”这一容易出错的操作,从程序员手动控制转移到了对象生命周期管理中,从而显著降低了内存泄漏的风险。

        例如下面这段代码,智能指针通过“对象析构自动释放资源”的机制,将资源管理问题转化为对象生命周期问题:

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

void test_raw() {
    int* p = new int(10);
    // 如果这里发生异常或提前 return,就会内存泄漏
    delete p;
}

void test_smart() {
    std::unique_ptr<int> p(new int(10));
    // 无需手动 delete,离开作用域自动释放
}

可以看到,智能指针通过“对象析构自动释放资源”的机制,将资源管理问题转化为对象生命周期问题,这正是现代 C++ 推荐的编程方式。

1.2.智能指针的调用

        智能指针定义在标准库的 <memory> 头文件中,位于 std 命名空间下,它们是 RAII(Resource Acquisition Is Initialization,资源获取即初始化)编程思想的重要体现。RAII 的核心思想是:资源在对象构造时获取,在对象析构时释放。也就是说,任何需要手动管理的资源(如堆内存、文件句柄、锁等),都应该交由一个栈对象来管理,这个对象在生命周期结束时自动执行清理逻辑,从而保证资源一定会被释放。例如:

#include <memory>

void func() {
    std::unique_ptr<int> p(new int(20));
    // 在函数结束时,无论是正常返回还是异常退出,p都会被析构,从而释放资源
}

        相比手动 new/delete,更加可靠和规范,也是现代 C++(尤其是 C++11 之后)推荐的资源管理范式。

1.3. 现阶段智能指针的使用

        在实际开发中,原始指针的使用范围已经被大大限制。通常情况下,当一个指针需要表示“资源所有权”时,应优先使用智能指针;而原始指针更多只用于不涉及所有权的场景,例如函数参数传递、临时访问、或者性能敏感且生命周期清晰的局部逻辑中。

        换句话说,现代 C++ 的设计理念是:用智能指针表达所有权,用原始指针表达观察关系。标准库中提供了多种智能指针类型,包括 auto_ptr(已弃用)、unique_ptrshared_ptrweak_ptr,它们分别针对不同的资源管理场景进行设计,在后续内容中我们会逐一展开分析它们的实现机制与使用方式。

2. 标准库提供的四类智能指针

        C++ 标准库中提供了四类典型的智能指针,分别是 auto_ptrunique_ptrshared_ptrweak_ptr。它们虽然都用于管理动态申请的资源,但设计目标并不相同:有的强调独占所有权,有的强调共享所有权,有的则专门用于解决循环引用问题

2.1.auto_ptr(已弃用)

  auto_ptr 是 C++98 时代提供的早期智能指针,可以在对象析构时自动释放所管理的内存。它采用的是一种“所有权模式”,当 auto_ptr 对象过期时,会通过析构函数自动调用 delete 释放资源。 但是它最大的缺陷在于:拷贝或赋值时会发生所有权转移,原对象会被置空。这意味着表面上看似普通的赋值操作,实际上会偷偷改变资源归属,代码行为不直观,也极易埋下错误隐患。除此之外,auto_ptr不支持数组管理,因为它内部使用的是 delete 而不是 delete[]。正因为这些设计问题,auto_ptr 在 C++11 中被弃用,并最终在后续标准中移除。

       这里仅做了解即可,所以不展出示例代码以免误导。

2.2 unique_ptr

  unique_ptr 是C++ 11中最常用的智能指针之一。它实现的是独占式拥有的概念,也就是说,同一时刻只能有一个 unique_ptr 指向某个对象。 这种设计可以非常明确地表达资源归属,避免多个对象同时“以为自己拥有资源”而导致重复释放问题。也正因为它强调独占所有权,所以 unique_ptr 不允许拷贝,也不允许普通赋值,如果确实要转移所有权,就必须显式使用 std::move

        它的基本调用方式如下:

std::unique_ptr<int> p(new int(10));

        或者更推荐的是这样写:

auto p = std::make_unique<int>(10);

        下面给出3个最基础的调用实例:

(1)所有权转移

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

int main() {
    auto u1 = std::make_unique<int>(10);

    // auto u2 = u1; // 编译错误,不允许拷贝

    auto u3 = std::move(u1);

    if (!u1) {
        cout << "u1 is nullptr" << endl;
    }

    cout << *u3 << endl;
    return 0;
}

这段代码中,u1 原本独占那块动态申请的内存,执行 std::move(u1) 后,资源所有权转移给了 u3,此时 u1 变为空,u3 成为新的唯一拥有者。这里的“不能拷贝、只能移动”正是 unique_ptr 最重要的行为特征。

(2)管理数组

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

int main() {
    auto arr = std::make_unique<int[]>(3);

    arr[0] = 10;
    arr[1] = 20;
    arr[2] = 30;

    for (int i = 0; i < 3; ++i) {
        cout << arr[i] << " ";
    }
    cout << endl;

    return 0;
}

unique_ptr 支持管理数组,并且会自动调用 delete[] 来释放资源,这一点比 auto_ptr 更安全。

(3)作为函数返回值

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

std::unique_ptr<int> createNumber() {
    return std::make_unique<int>(20);
}

int main() {
    auto ptr = createNumber();
    cout << *ptr << endl;
    return 0;
}

作为函数返回值来转移资源所有权,在这个例子中,函数内部创建资源,返回给调用者后,由调用者继续接管该资源。整个过程不需要手动 delete,同时又能明确表达“这份资源只有一个所有者”。

2.3 shared_ptr

        如果说 unique_ptr 强调“一个资源只能有一个主人”,那么 shared_ptr 则适用于“一个资源可以被多个对象共同拥有”的场景。根据你的笔记,shared_ptr 实现的是共享式拥有的概念,它通过引用计数机制来管理对象生命周期:每多一个 shared_ptr 指向该对象,计数加一;每少一个,计数减一;当计数减为 0 时,对象才会被自动释放。

        基本调用方式如下:

std::shared_ptr<int> p(new int(10));

        推荐写为现在这个形式:

auto p = std::make_shared<int>(10);

        shared_ptr 很适合一些“共享资源”的轻量级实战场景,例如多个对象共同使用同一份配置数据:

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

class Config {
public:
    Config() { cout << "Config created" << endl; }
    ~Config() { cout << "Config destroyed" << endl; }

    void show() {
        cout << "using shared config" << endl;
    }
};

int main() {
    std::shared_ptr<Config> c1 = std::make_shared<Config>();
    std::shared_ptr<Config> c2 = c1;

    c1->show();
    c2->show();

    cout << "count = " << c1.use_count() << endl;
    return 0;
}

这个例子中,c1c2 共享同一个 Config 对象,直到它们都离开作用域时,析构函数才会执行。这种模式在缓存、资源池、共享配置、图结构节点等场景下非常常见。

2.4. weak_ptr

  weak_ptr 可以理解为一种“弱观察者”。它本身并不拥有对象,也不会增加引用计数,只是用来观察一个由 shared_ptr 管理的对象是否仍然存在。weak_ptr 的主要作用有两个:

  • 协助 shared_ptr 解决循环引用问题
  • 在不影响对象生命周期的前提下观察对象状态

        它不能单独使用,必须依附于 shared_ptr

std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp;

        由于 weak_ptr 不拥有对象,因此它不能直接解引用,必须先通过 lock() 尝试提升为一个有效的 shared_ptr,然后再访问对象。先看一个最基础的调用示例:

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

int main() {
    std::shared_ptr<int> s1(new int(10));
    std::weak_ptr<int> w1 = s1;

    if (std::shared_ptr<int> s2 = w1.lock()) {
        cout << *s2 << endl;
    } else {
        cout << "object has been deleted" << endl;
    }

    s1.reset();

    if (std::shared_ptr<int> s3 = w1.lock()) {
        cout << *s3 << endl;
    } else {
        cout << "object has been deleted" << endl;
    }

    return 0;
}
  1. 这段代码中,第一次 lock() 时对象仍然存在,所以可以成功拿到一个新的 shared_ptr 并访问数据;当 s1.reset() 后,原对象被销毁,再次 lock() 就会返回空指针,从而避免访问悬空对象。
  2. weak_ptr 最典型的实战用途,是解决 shared_ptr 的循环引用问题。比如给出的 AB 相互引用的例子非常典型:如果双方都用 shared_ptr,那么即使函数结束,引用计数也无法降到 0,对象就永远不会被释放。
#include <iostream>
#include <memory>
using namespace std;

class B;

class A {
public:
    std::weak_ptr<B> pb;  // 改成 weak_ptr,避免循环引用
    ~A() { cout << "A destroyed" << endl; }
};

class B {
public:
    std::shared_ptr<A> pa;
    ~B() { cout << "B destroyed" << endl; }
};

void test() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->pb = b;
    b->pa = a;
}

int main() {
    test();
    return 0;
}

在这个例子中,B 仍然通过 shared_ptr 拥有 A,但 A 只通过 weak_ptr 观察 B,因此不会增加 B 的引用计数,最终就能成功打破循环引用。函数结束后,对象会正常析构。

  weak_ptr 不负责拥有资源,只负责观察资源,它最重要的价值在于避免 shared_ptr 之间形成循环引用。此外,在一些缓存、观察者模式、事件回调等场景中,weak_ptr 也很常用,因为它能安全判断对象是否还存在,而不会延长对象寿命。

3. 代码实战

#include <iostream>  
#include <memory>     // 智能指针头文件
#include <string>     

using namespace std;

// ======================================================
// 第一部分:定义一个简单的角色类,用来演示 unique_ptr 和 shared_ptr
// ======================================================
class Player {
private:
    string name;   // 玩家名字

public:
    // 构造函数:创建对象时输出提示,方便观察对象何时被创建
    Player(const string& n) : name(n) {
        cout << "Player " << name << " created." << endl;
    }

    // 析构函数:对象销毁时输出提示,方便观察对象何时被释放
    ~Player() {
        cout << "Player " << name << " destroyed." << endl;
    }

    // 普通成员函数:输出玩家信息
    void show() const {
        cout << "Player name: " << name << endl;
    }
};

// ======================================================
// 第二部分:演示 unique_ptr 作为函数返回值
// 说明:unique_ptr 支持所有权转移,适合作为函数返回值
// ======================================================
unique_ptr<Player> createPlayer(const string& name) {
    // 使用 make_unique 创建对象,推荐写法,避免直接裸写 new
    return make_unique<Player>(name);
}

// ======================================================
// 第三部分:定义两个类,演示 weak_ptr 解决循环引用
// 场景:Teacher 和 Student 彼此关联
// 如果双方都用 shared_ptr,就可能产生循环引用
// ======================================================
class Student;  // 前向声明,告诉编译器 Student 类后面会定义

class Teacher {
public:
    string name;                  // 教师姓名
    weak_ptr<Student> student;    // 用 weak_ptr 指向学生,避免循环引用

    Teacher(const string& n) : name(n) {
        cout << "Teacher " << name << " created." << endl;
    }

    ~Teacher() {
        cout << "Teacher " << name << " destroyed." << endl;
    }
};

class Student {
public:
    string name;                  // 学生姓名
    shared_ptr<Teacher> teacher;  // 学生持有教师,表示共享关系

    Student(const string& n) : name(n) {
        cout << "Student " << name << " created." << endl;
    }

    ~Student() {
        cout << "Student " << name << " destroyed." << endl;
    }
};

// ======================================================
// 主函数:统一演示三类现代智能指针
// ======================================================
int main() {

    cout << "================ unique_ptr 演示 ================" << endl;

    // --------------------------------------------------
    // 1. unique_ptr:独占所有权
    // 一个对象同一时刻只能被一个 unique_ptr 管理
    // --------------------------------------------------
    auto p1 = make_unique<Player>("Knight");

    // 调用对象成员函数
    p1->show();

    // unique_ptr 不允许拷贝,下面这句如果取消注释会编译报错
    // auto p2 = p1;

    // 只能通过 move 转移所有权
    auto p2 = move(p1);

    // 转移后,p1 变为空
    if (!p1) {
        cout << "p1 is now nullptr after move." << endl;
    }

    // 现在资源由 p2 独占
    p2->show();

    cout << endl;
    cout << "================ unique_ptr 作为返回值 ================" << endl;

    // --------------------------------------------------
    // 2. unique_ptr 作为函数返回值
    // createPlayer 返回一个 unique_ptr,所有权安全转移给 hero
    // --------------------------------------------------
    auto hero = createPlayer("Archer");
    hero->show();

    cout << endl;
    cout << "================ shared_ptr 演示 ================" << endl;

    // --------------------------------------------------
    // 3. shared_ptr:共享所有权
    // 多个 shared_ptr 可以共同管理同一个对象
    // --------------------------------------------------
    auto sp1 = make_shared<Player>("Mage");

    // 让 sp2 也指向同一个对象
    auto sp2 = sp1;

    // use_count() 用于查看当前引用计数
    cout << "sp1 use_count: " << sp1.use_count() << endl;
    cout << "sp2 use_count: " << sp2.use_count() << endl;

    // 通过其中任意一个智能指针都可以访问对象
    sp1->show();
    sp2->show();

    // reset() 表示当前 shared_ptr 放弃管理权
    sp1.reset();

    // 此时对象还不会被销毁,因为 sp2 仍然指向它
    cout << "After sp1.reset(), sp2 use_count: " << sp2.use_count() << endl;
    sp2->show();

    cout << endl;
    cout << "================ weak_ptr 演示 ================" << endl;

    // --------------------------------------------------
    // 4. weak_ptr:弱引用,不增加引用计数
    // 用来解决 shared_ptr 循环引用问题
    // --------------------------------------------------
    {
        // 创建学生对象和教师对象
        auto stu = make_shared<Student>("Alice");
        auto tea = make_shared<Teacher>("Mr.Wang");

        // 学生持有教师:shared_ptr
        stu->teacher = tea;

        // 教师只观察学生:weak_ptr
        tea->student = stu;

        // weak_ptr 不能直接访问对象,必须先 lock()
        if (auto lockedStudent = tea->student.lock()) {
            cout << "Teacher can see student: " << lockedStudent->name << endl;
        } else {
            cout << "Student object has been destroyed." << endl;
        }

        // 这里代码块结束后:
        // stu 和 tea 都会离开作用域
        // 因为 Teacher 持有的是 weak_ptr,不会形成循环引用
        // 所以 Student 和 Teacher 都能正常析构
    }

    cout << endl;
    cout << "================ 程序结束 ================" << endl;

    return 0;
}

1. 第一段演示的是 unique_ptr。这里重点体现了它的独占所有权:不能拷贝,只能通过 move 转移;并且统一采用了推荐写法 make_unique,和前文讲解保持一致。

2. 第二段演示的是 unique_ptr 作为函数返回值。这正好对应提到的“可以高效地将资源所有权转移给调用者”这一点。

3. 第三段演示的是 shared_ptr。通过两个智能指针共同管理同一个 Player 对象,展示了引用计数的变化过程,这也是 shared_ptr 最核心的机制。

4. 第四段演示的是 weak_ptr。用 TeacherStudent 的相互关联来说明:如果双方都使用 shared_ptr,就会形成循环引用;而改成一方使用 weak_ptr 后,就可以正常释放资源。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐