(1)常见智能指针类型

智能指针

所在头文件

特点

适用场景

std::unique_ptr

<memory>

独占式所有权,不能复制,只能移动

独占资源,自动释放

std::shared_ptr

<memory>

引用计数,多个智能指针共享资源

多个对象共享同一资源

std::weak_ptr

<memory>

辅助 shared_ptr,不增加引用计数

解决循环引用问题,观察资源是否释放

std::auto_ptr

<memory>(已废弃)

C++98 提供,不安全

已废弃,避免使用

(2)核心概念与行为说明

  1. unique_ptr

  • 独占所有权,不能复制,只能移动;

  • 适合用于 RAII 管理资源

  • 默认会调用 delete 释放资源,可以自定义 deleter。

  1. shared_ptr

  • 采用引用计数进行资源共享;

  • 可以复制,共享相同的底层对象

  • 最后一个 shared_ptr 被销毁时释放资源;

  • 可以和 make_shared 搭配使用,更高效内存分配

  1. weak_ptr

  • 不拥有资源,只观察 shared_ptr 所管理的对象;

  • 用于打破 shared_ptr 之间的 循环引用

  • 需要通过 lock() 转为 shared_ptr 使用。

(3)面试常见问题及简要回答

问题

快速答法

unique_ptr 与 shared_ptr 区别?

前者独占资源,不支持复制;后者支持共享引用计数。

为什么 shared_ptr 会造成循环引用?如何解决?

循环引用中 shared_ptr 相互持有,引用计数无法归零,使用 weak_ptr 避免。

使用 make_shared 有什么好处?

更高效:一次内存分配,同时存对象与控制块,避免 shared_ptr(new T)。

weak_ptr 为什么不能直接使用?

因为它不拥有资源,需要通过 lock() 临时转为 shared_ptr 使用。

shared_ptr 是否线程安全?

引用计数线程安全,对象本身不是。

自定义 deleter 有哪些场景?

资源不是通过 delete 释放,比如使用 C 接口分配的内存(如 fclose 等)。

13.1 unique_ptr

std::unique_ptr是一个独占所有权的智能指针,它确保同一时间内只有一个指针可以拥有某个对象的所有权。适合用于一个对象只能被一个指针拥有的情况。

示例1:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass Constructor" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass Destructor" << std::endl;
    }
    void display() {
        std::cout << "Display method of MyClass" << std::endl;
    }
};

int main() {
    // 创建一个unique_ptr对象,管理MyClass实例
    std::unique_ptr<MyClass> ptr1(new MyClass());
    ptr1->display(); // 使用智能指针调用方法
    
    //类比一下vector <int> vec;

    // 转移所有权
    std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
    if (!ptr1) {
        std::cout << "ptr1 is now null" << std::endl;
    }

    ptr2->display(); // 使用新的智能指针调用方法

    // ptr2超出作用域,MyClass对象自动销毁
    return 0;
}

示例2:

std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
// std::unique_ptr<int> ptr2 = ptr1; // ❌ 编译错误
std::unique_ptr<int> ptr3 = std::move(ptr1); // ✅ 合法

13.2 shared_ptr

std::shared_ptr是一个共享所有权的智能指针,可以被多个指针共享。它通过引用计数来管理对象的生命周期,当最后一个std::shared_ptr销毁时,对象才会被释放。

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() {
        std::cout << "MyClass Constructor" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass Destructor" << std::endl;
    }
    void display() {
        std::cout << "Display method of MyClass" << std::endl;
    }
};

int main() {
    // 创建一个shared_ptr对象,管理MyClass实例
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    {
        // 创建第二个shared_ptr,共享相同的对象
        std::shared_ptr<MyClass> ptr2 = ptr1;
        ptr2->display(); // 使用智能指针调用方法

        std::cout << "ptr2 going out of scope" << std::endl;
    } // ptr2超出作用域,但对象未销毁,因为ptr1还在使用

    ptr1->display(); // 使用剩余的智能指针调用方法

    // ptr1超出作用域,MyClass对象自动销毁
    return 0;
}

13.3 weak_ptr

示例1:

#include <iostream>
#include <memory>

class MyClass {
public:
    std::shared_ptr<MyClass> other;

    MyClass() {
        std::cout << "MyClass Constructor" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass Destructor" << std::endl;
    }
    void display() {
        std::cout << "Display method of MyClass" << std::endl;
    }
};

int main() {
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();

    // 使用weak_ptr避免循环引用
    ptr1->other = ptr2;
    ptr2->other = ptr1;

    ptr1->display();
    ptr2->display();

    // ptr1和ptr2超出作用域,MyClass对象自动销毁
    return 0;
}

在这个例子中,如果other成员是std::shared_ptr,会导致循环引用,两个对象都不会被销毁。使用std::weak_ptr可以避免这个问题,因为它不增加引用计数。

如果要使用 std::weak_ptr 来避免循环引用,可以对 MyClassother 成员使用 std::weak_ptr,而不是 std::shared_ptr。这样做可以防止形成循环引用,从而避免对象无法正确释放的问题。下面是修改后的示例代码:

#include <iostream>
#include <memory>

class MyClass {
public:
    std::weak_ptr<MyClass> other;  // 使用 weak_ptr 避免循环引用

    MyClass() {
        std::cout << "MyClass Constructor" << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass Destructor" << std::endl;
    }
    void display() {
        std::cout << "Display method of MyClass" << std::endl;
    }
};

int main() {
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();

    // 使用 weak_ptr 来避免循环引用
    ptr1->other = ptr2;
    ptr2->other = ptr1;

    ptr1->display();
    ptr2->display();

    // ptr1 和 ptr2 超出作用域,MyClass 对象会正确释放
    return 0;
}

说明:

  • std::shared_ptr<MyClass> other; 修改为 std::weak_ptr<MyClass> other;。这样做将 other 成员变量改为弱引用,不会增加 MyClass 对象的引用计数,也不会导致循环引用。

  • ptr1->other = ptr2;ptr2->other = ptr1; 分别将 ptr2ptr1 赋给对方的 other 成员,但是这里使用了 std::weak_ptr,因此不会导致循环引用问题。

  • 程序的输出和行为应该与之前的示例相同,即成功创建两个 MyClass 对象,并且在 ptr1ptr2 超出作用域时,对象会被正确销毁。

示例2:

std::shared_ptr<int> sp1 = std::make_shared<int>(100);
std::shared_ptr<int> sp2 = sp1;  // 引用计数 +1

std::weak_ptr<int> wp = sp1;
if (auto sp3 = wp.lock()) {
    std::cout << *sp3 << std::endl;
}

13.4 总结

🧠 智能指针底层机制详解

一、shared_ptr 的实现核心

shared_ptr 由两部分组成:

  • 资源指针(T*): 指向实际管理的对象;

  • 控制块(Control Block): 存放引用计数信息与删除器(Deleter);

🔧 控制块内容

字段名

说明

strong_count

当前共享引用计数

weak_count

当前弱引用计数

deleter

删除资源的函数指针

共享对象释放流程:

  • 每次复制 shared_ptr,strong_count++

  • 每次销毁 shared_ptr,strong_count--

  • 当 strong_count 0: -> 执行 deleter 释放资源 -> 若 weak_count 0,释放控制块

二、线程安全性说明

操作

是否线程安全

说明

引用计数增加/减少

✅ 是

使用原子操作保证多线程中计数更新安全

被管理对象的访问

❌ 否

多线程访问需加锁,否则存在数据竞争

weak_ptr.lock() 转换

✅ 是

内部引用计数安全,转换返回新的 shared_ptr

🎯 面试进阶常问问题

问题

快速答法

shared_ptr 如何实现引用计数?

通过控制块中的原子计数器管理 strong/weak 引用数。

weak_ptr 怎么避免循环引用?

它不增加引用计数,只是观察者,配合 shared_ptr 使用打破循环依赖。

shared_ptr 和 make_shared 的区别?

make_shared 效率更高,减少内存分配次数,构造对象与控制块在一块。

unique_ptr 可以作为函数参数吗?为什么使用右值引用?

可以,使用 T&& 实现所有权转移,避免资源复制。

shared_ptr 是否适合用于资源池或频繁分配释放场景?

不适合,引用计数与控制块开销较大,应考虑对象池或手动管理。

如何自定义 deleter?

可以传入 lambda、函数指针或仿函数到 shared_ptr,释放特殊资源如 FILE*。