📚 C++智能指针完全指南

📑 目录

🎯 本节目标

  1. 🔍 为什么需要智能指针?
  2. 🚨 内存泄漏详解
  3. ⚙️ 智能指针的使用及原理
  4. 🔗 C++11和Boost中智能指针的关系
  5. 📖 RAII扩展学习

🔍 1. 为什么需要智能指针?

让我们先分析下面这段程序的内存管理问题:

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

int div()
{
    int a, b;
    cin >> a >> b;
    if (b == 0)
        throw invalid_argument("除0错误");
    return a / b;
}

void Func()
{
    // 🚨 三个潜在的内存泄漏风险点:
    // 1. 如果p1这里new抛异常会如何?
    // 2. 如果p2这里new抛异常会如何?
    // 3. 如果div调用这里抛异常会如何?
    int* p1 = new int;
    int* p2 = new int;
    cout << div() << endl;
    delete p1;
    delete p2;
}

int main()
{
    try
    {
        Func();
    }
    catch (exception& e)
    {
        cout << e.what() << endl;
    }
    return 0;
}

⚠️ 问题分析

div()函数抛出异常后,程序便终止了,后面的delete p1delete p2便不会再执行,最终导致内存泄漏

🚨 2. 内存泄漏详解

2.1 什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害

  • 🐌 长期运行的程序出现内存泄漏,影响很大
  • 💻 操作系统、后台服务等出现内存泄漏会导致响应越来越慢
  • ⛔ 最终可能导致系统卡死或崩溃

2.2 内存泄漏分类(了解)

C/C++程序中一般我们关心两种方面的内存泄漏:

🔧 堆内存泄漏 (Heap leak)

堆内存指的是程序执行中依据需要分配通过malloc/calloc/realloc/new等从堆中分配的一块内存,用完后必须通过调用相应的free或者delete删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

🌐 系统资源泄漏

指程序使用系统分配的资源,比如套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

2.3 如何检测内存泄漏(了解)

  • Linux下内存泄漏检测工具

    • Valgrind
    • AddressSanitizer
    • LeakSanitizer
  • Windows下使用第三方工具

    • Visual Leak Detector (VLD)
    • Dr. Memory
    • Application Verifier

2.4 如何避免内存泄漏

  1. 📐 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放

    • 💡 理想状态,但如果碰上异常时,就算注意释放了,还是可能会出问题
  2. 🛡️ 采用RAII思想或者智能指针来管理资源

  3. 🏢 有些公司内部规范使用内部实现的私有内存管理库,这套库自带内存泄漏检测的功能选项

  4. 🔍 出问题了使用内存泄漏工具检测

void MemoryLeaks()
{
    // 1. 🚨 内存申请了忘记释放
    int* p1 = (int*)malloc(sizeof(int));
    int* p2 = new int;
    
    // 2. ⚠️ 异常安全问题
    int* p3 = new int[10];
    Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放
    delete[] p3;
}

📝 总结

内存泄漏非常常见,解决方案分为两种:

  1. 🛡️ 事前预防型:如智能指针等
  2. 🔍 事后查错型:如泄漏检测工具

⚙️ 3. 智能指针的使用及原理

3.1 RAII(资源获取即初始化)

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

核心思想

  • 🏗️ 在对象构造时获取资源
  • 🔒 控制对资源的访问使之在对象的生命周期内始终保持有效
  • 🗑️ 最后在对象析构的时候释放资源

两大好处

  1. ✅ 不需要显式地释放资源
  2. ✅ 对象所需的资源在其生命期内始终保持有效
// 🛠️ 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
    SmartPtr(T* ptr = nullptr)
        : _ptr(ptr)
    {}
    
    ~SmartPtr()
    {
        if(_ptr)
            delete _ptr;
    }
    
private:
    T* _ptr;
};

// 🎯 使用示例
void Func()
{
    SmartPtr<int> sp1(new int);
    SmartPtr<int> sp2(new int);
    cout << div() << endl;
    // 🎉 无需手动delete,析构时自动释放
}

3.2 智能指针的原理

上述的SmartPtr还不能称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容。

template<class T>
class SmartPtr {
public:
    SmartPtr(T* ptr = nullptr)
        : _ptr(ptr)
    {}
    
    ~SmartPtr()
    {
        if(_ptr)
            delete _ptr;
    }
    
    // 🔧 重载解引用运算符
    T& operator*() { return *_ptr; }
    
    // 🔧 重载箭头运算符
    T* operator->() { return _ptr; }
    
private:
    T* _ptr;
};

// 📅 使用示例
struct Date
{
    int _year;
    int _month;
    int _day;
};

int main()
{
    SmartPtr<int> sp1(new int);
    *sp1 = 10;
    cout << *sp1 << endl;  // 输出: 10
    
    SmartPtr<Date> sparray(new Date);
    // 注意:这里应该是sparray.operator->()->_year = 2018;
    // 语法上为了可读性,省略了一个->
    sparray->_year = 2018;
    sparray->_month = 1;
    sparray->_day = 1;
    
    return 0;
}

📋 智能指针原理总结:

  1. 🛡️ RAII特性:资源获取即初始化
  2. 🔧 重载operator*和operator->:具有像指针一样的行为

3.3 std::auto_ptr(C++98)

C++98版本的库中提供了auto_ptr的智能指针,但其设计存在严重问题(会出现悬空指针的问题,即原来指向资源的指针被置空后再次被使用)。

// 🚫 C++98 管理权转移 auto_ptr(失败的设计)
namespace bit
{
    template<class T>
    class auto_ptr
    {
    public:
        auto_ptr(T* ptr)
            :_ptr(ptr)
        {}
        
        // 🚨 管理权转移的拷贝构造函数
        auto_ptr(auto_ptr<T>& sp)
            :_ptr(sp._ptr)
        {
            sp._ptr = nullptr;  // 原指针被置空!
        }
        
        auto_ptr<T>& operator=(auto_ptr<T>& ap)
        {
            if (this != &ap)
            {
                if (_ptr)
                    delete _ptr;
                _ptr = ap._ptr;
                ap._ptr = nullptr;  // 管理权转移
            }
            return *this;
        }
        
        ~auto_ptr()
        {
            if (_ptr)
            {
                cout << "delete:" << _ptr << endl;
                delete _ptr;
            }
        }
        
        T& operator*() { return *_ptr; }
        T* operator->() { return _ptr; }
        
    private:
        T* _ptr;
    };
}

// ⚠️ 演示auto_ptr的问题
int main()
{
    std::auto_ptr<int> sp1(new int);
    std::auto_ptr<int> sp2(sp1);  // 管理权转移
    
    // 🚨 sp1现在悬空了!
    *sp2 = 10;
    cout << *sp2 << endl;  // 正常:10
    // cout << *sp1 << endl;  // ❌ 运行时错误:悬空指针
    
    return 0;
}

结论auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr

3.4 std::unique_ptr(C++11)

C++11中提供了更靠谱的unique_ptr,采用简单粗暴的防拷贝策略。

// ✅ C++11 unique_ptr - 防拷贝实现
namespace bit
{
    template<class T>
    class unique_ptr
    {
    public:
        unique_ptr(T* ptr)
            :_ptr(ptr)
        {}
        
        ~unique_ptr()
        {
            if (_ptr)
            {
                cout << "delete:" << _ptr << endl;
                delete _ptr;
            }
        }
        
        // 🔧 像指针一样使用
        T& operator*() { return *_ptr; }
        T* operator->() { return _ptr; }
        
        // 🚫 禁止拷贝
        unique_ptr(const unique_ptr<T>& sp) = delete;
        unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
        
        // ✅ 允许移动语义
        unique_ptr(unique_ptr<T>&& sp) noexcept
            :_ptr(sp._ptr)
        {
            sp._ptr = nullptr;
        }
        
        unique_ptr<T>& operator=(unique_ptr<T>&& sp) noexcept
        {
            if (this != &sp)
            {
                if (_ptr) delete _ptr;
                _ptr = sp._ptr;
                sp._ptr = nullptr;
            }
            return *this;
        }
        
    private:
        T* _ptr;
    };
}

// 🎯 使用示例
int main()
{
    bit::unique_ptr<int> sp1(new int(42));
    // bit::unique_ptr<int> sp2(sp1);  // ❌ 编译错误:拷贝构造被删除
    
    bit::unique_ptr<int> sp3(std::move(sp1));  // ✅ 允许移动构造
    *sp3 = 100;
    cout << *sp3 << endl;  // 输出: 100
    
    return 0;
}

3.5 std::shared_ptr(C++11)

shared_ptr通过引用计数的方式实现多个shared_ptr对象之间共享资源。

原理

  1. 📊 shared_ptr在其内部给每个资源都维护着一份计数,用来记录该份资源被几个对象共享
  2. 🔽 在对象被销毁时(析构函数调用),说明自己不使用该资源了,对象的引用计数减一
  3. 🗑️ 如果引用计数是0,说明自己是最后一个使用该资源的对象,必须释放该资源
  4. 🔒 如果不是0,说明除了自己还有其他对象在使用该份资源,不能释放该资源
// 🛠️ shared_ptr简化实现(带线程安全)
#include <iostream>
#include <mutex>
using namespace std;

namespace bit
{
    template<class T>
    class shared_ptr
    {
    public:
        shared_ptr(T* ptr = nullptr)
            :_ptr(ptr)
            , _pRefCount(new int(1))
            , _pmtx(new mutex)
        {}
        
        shared_ptr(const shared_ptr<T>& sp)
            :_ptr(sp._ptr)
            , _pRefCount(sp._pRefCount)
            , _pmtx(sp._pmtx)
        {
            AddRef();
        }
        
        void Release()
        {
            _pmtx->lock();
            bool flag = false;//确定释放mutex资源的条件
            if (--(*_pRefCount) == 0 && _ptr)
            {
                cout << "delete:" << _ptr << endl;
                delete _ptr;
                delete _pRefCount;
                //该处不能释放_pmtx,如果释放了,lock()和unlock()就失效了
                flag = true;
            }
            _pmtx->unlock();
            
            if (flag == true)
            {
                delete _pmtx;
            }
        }
        
        void AddRef()
        {
            _pmtx->lock();
            ++(*_pRefCount);
            _pmtx->unlock();
        }
        
        shared_ptr<T>& operator=(const shared_ptr<T>& sp)
        {
        	// if(this != &sp)
            if (_ptr != sp._ptr)
            {
                Release();
                _ptr = sp._ptr;
                _pRefCount = sp._pRefCount;
                _pmtx = sp._pmtx;
                AddRef();
            }
            return *this;
        }
        
        int use_count() const
        {
            _pmtx->lock();
            int count = *_pRefCount;
            _pmtx->unlock();
            return count;
        }
        
        ~shared_ptr()
        {
            Release();
        }
        
        T& operator*() { return *_ptr; }
        T* operator->() { return _ptr; }
        T* get() const { return _ptr; }
        
    private:
        T* _ptr;
        int* _pRefCount;
        mutex* _pmtx;
    };
}

// 🎯 使用示例
int main()
{
    bit::shared_ptr<int> sp1(new int(10));
    cout << "sp1 use_count: " << sp1.use_count() << endl;  // 1
    
    {
        bit::shared_ptr<int> sp2(sp1);
        cout << "sp1 use_count: " << sp1.use_count() << endl;  // 2
        cout << "sp2 use_count: " << sp2.use_count() << endl;  // 2
    }
    
    cout << "sp1 use_count: " << sp1.use_count() << endl;  // 1
    
    return 0;
}

🔒 std::shared_ptr的线程安全问题

shared_ptr的线程安全分为两方面:

  1. 引用计数的线程安全

    • 智能指针对象中引用计数是多个智能指针对象共享的
    • 两个线程中智能指针的引用计数同时++--,这个操作不是原子的
    • 引用计数原来是1,++了两次,可能还是2,这样引用计数就错乱了
    • 会导致资源未释放或者程序崩溃的问题
    • 所以智能指针中引用计数++--是需要加锁的
  2. 管理资源的线程安全

    • 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题
    • 这个问题需要用户自己通过加锁解决
// 1.演示引用计数线程安全问题,就把AddRefCount和SubRefCount中的锁去掉
// 2.演示可能不出现线程安全问题,因为线程安全问题是偶现性问题,main函数的n改大一些概率就变大了,就容易出现了。
// 3.下面代码我们使用SharedPtr演示,是为了方便演示引用计数的线程安全问题,将代码中的SharedPtr换成shared_ptr进行测试,可以验证库的shared_ptr,发现结论是一样的。
#include <thread>
#include <mmeory>
#include<iostream>
using namespace std;

struct Date
{
    int _year = 0;
    int _month = 0;
    int _day = 0;
};

void SharePtrFunc(bit::shared_ptr<Date>& sp, size_t n, mutex& mtx)
{
    for (size_t i = 0; i < n; ++i)
    {
        // ✅ 这里智能指针拷贝会++计数,智能指针析构会--计数,线程安全
        bit::shared_ptr<Date> copy(sp);
        
        // 🔒 访问资源需要用户自己加锁
        {
            lock_guard<mutex> lock(mtx);
            copy->_year++;
            copy->_month++;
            copy->_day++;
        }
    }
}

int main()
{
    bit::shared_ptr<Date> p(new Date);
    const size_t n = 100000;
    mutex mtx;
    
    thread t1(SharePtrFunc, std::ref(p), n, std::ref(mtx));
    thread t2(SharePtrFunc, std::ref(p), n, std::ref(mtx));
    
    t1.join();
    t2.join();
    
    cout << "Year: " << p->_year << endl;    // 应该是200000
    cout << "Month: " << p->_month << endl;  // 应该是200000
    cout << "Day: " << p->_day << endl;      // 应该是200000
    cout << "use_count: " << p.use_count
    return 0;
 }

🔄 std::shared_ptr的循环引用问题

循环引用是shared_ptr的一个经典问题:

#include <memory>
using namespace std;

struct ListNode
{
    int _data;
    shared_ptr<ListNode> _prev;
    shared_ptr<ListNode> _next;
    
    ~ListNode() { 
        cout << "~ListNode()" << endl; 
    }
};

int main()
{
    shared_ptr<ListNode> node1(new ListNode);
    shared_ptr<ListNode> node2(new ListNode);
    
    cout << "node1 use_count: " << node1.use_count() << endl;  // 1
    cout << "node2 use_count: " << node2.use_count() << endl;  // 1
    
    node1->_next = node2;  // node2引用计数变为2
    node2->_prev = node1;  // node1引用计数变为2
    
    cout << "node1 use_count: " << node1.use_count() << endl;  // 2
    cout << "node2 use_count: " << node2.use_count() << endl;  // 2
    
    return 0;
    // 🚨 内存泄漏!node1和node2都不会被释放
}

循环引用分析

  1. 📌 node1node2两个智能指针对象指向两个节点,引用计数变成1
  2. 🔗 node1_next指向node2node2_prev指向node1
  3. 🔄 形成循环引用: node1node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 📉 也就是说_next析构了,node2就释放了。
  5. 🚨 也就是说_prev析构了,node1就释放了。
  6. 🚨 但是_next属于node的成员,node1释放了,_next才会析构,而node1_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。
  7. 💥 结果:两个节点的内存都无法释放,造成内存泄漏

循环引用图示
在这里插入图片描述

3.6 std::weak_ptr(C++11)

weak_ptr就是为了解决shared_ptr的循环引用问题而设计的。

weak_ptr的特点

  • 🔗 不控制对象的生命周期,不会增加引用计数
  • 👁️ 只能观察对象,不能直接访问对象
  • 🔄 需要配合shared_ptr使用
  • 🔓 通过lock()方法获取可用的shared_ptr

使用weak_ptr解决循环引用

// 解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
// 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
#include <memory>
#include<iostream>
using namespace std;

struct ListNode
{
    int _data;
    weak_ptr<ListNode> _prev;  // 使用weak_ptr
    weak_ptr<ListNode> _next;  // 使用weak_ptr
    
    ~ListNode() { 
        cout << "~ListNode()" << endl; 
    }
};

int main()
{
    shared_ptr<ListNode> node1(new ListNode);
    shared_ptr<ListNode> node2(new ListNode);
    
    node1->_next = node2;  // weak_ptr赋值,不增加引用计数
    node2->_prev = node1;  // weak_ptr赋值,不增加引用计数
    
    cout << "node1 use_count: " << node1.use_count() << endl;  // 1
    cout << "node2 use_count: " << node2.use_count() << endl;  // 1
    
    // ✅ 正常释放,无内存泄漏
    return 0;
}

weak_ptr常用方法

shared_ptr<int> sp(new int(42));
weak_ptr<int> wp(sp);  // 构造weak_ptr

// 1. use_count() - 返回共享对象的shared_ptr数量
cout << wp.use_count() << endl;  // 1

// 2. expired() - 检查对象是否已被释放
if (wp.expired()) {
    cout << "对象已被释放" << endl;
}

// 3. lock() - 获取shared_ptr,如果对象存在
if (auto sp2 = wp.lock()) {
    cout << *sp2 << endl;  // 42
} else {
    cout << "对象已被释放" << endl;
}

// 4. reset() - 释放对对象的观察
wp.reset();

3.7 智能指针的选择指南

智能指针 适用场景 所有权 线程安全 性能
unique_ptr 独占所有权,简单场景 独占 非线程安全 ⭐⭐⭐⭐⭐
shared_ptr 共享所有权,复杂场景 共享 引用计数原子操作安全 ⭐⭐⭐
weak_ptr 解决循环引用,观察者模式 无所有权 非线程安全 ⭐⭐⭐⭐

选择建议

  1. 🎯 优先使用unique_ptr:大多数情况下,unique_ptr就足够了
  2. 🤝 需要共享时用shared_ptr:多个对象需要共享同一资源时
  3. 🔄 循环引用用weak_ptr:存在循环引用风险时,使用weak_ptr打破循环
  4. 🚫 避免使用auto_ptr:已被C++11弃用,有潜在问题

3.8 自定义删除器

如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这个问题

#include <memory>
#include <iostream>
#include <cstdio>

// 1. 函数指针作为删除器
void FileDeleter(FILE* fp) {
    if (fp) {
        fclose(fp);
        std::cout << "文件已关闭" << std::endl;
    }
}

// 2. 函数对象作为删除器
struct FileCloser {
    void operator()(FILE* fp) const {
        if (fp) {
            fclose(fp);
            std::cout << "文件已关闭(函数对象)" << std::endl;
        }
    }
};

// 3. lambda表达式作为删除器
auto lambdaDeleter = [](FILE* fp) {
    if (fp) {
        fclose(fp);
        std::cout << "文件已关闭(lambda)" << std::endl;
    }
};

int main() {
    // 使用函数指针
    std::unique_ptr<FILE, decltype(&FileDeleter)> 
        file1(fopen("test.txt", "w"), FileDeleter);
    
    // 使用函数对象
    std::unique_ptr<FILE, FileCloser> 
        file2(fopen("test.txt", "w"));
    
    // 使用lambda(C++17起)
    std::unique_ptr<FILE, decltype(lambdaDeleter)> 
        file3(fopen("test.txt", "w"), lambdaDeleter);
    
    // shared_ptr的删除器在构造时指定,不影响类型
    std::shared_ptr<FILE> 
        file4(fopen("test.txt", "w"), FileDeleter);
    
    return 0;
}

3.9 智能指针与数组

unique_ptr管理数组

// C++11/14:需要指定删除器
std::unique_ptr<int[]> arr1(new int[10]);

// C++17起:可以直接使用
std::unique_ptr<int[]> arr2 = std::make_unique<int[]>(10);

// 访问数组元素
for (int i = 0; i < 10; ++i) {
    arr2[i] = i * i;
}

// 自动调用delete[]

shared_ptr管理数组(需要自定义删除器)

// 需要自定义删除器
std::shared_ptr<int> arr3(new int[10], [](int* p) { delete[] p; });

// C++17起:可以使用默认的删除器
std::shared_ptr<int[]> arr4(new int[10]);

// 或者使用make_shared(C++20起支持数组)
// std::shared_ptr<int[]> arr5 = std::make_shared<int[]>(10);

3.10 智能指针的最佳实践

  1. 优先使用make_uniquemake_shared

    // 推荐
    auto sp1 = std::make_shared<Widget>();
    auto up1 = std::make_unique<Widget>();
    
    // 不推荐
    std::shared_ptr<Widget> sp2(new Widget());
    
  2. 避免裸指针与智能指针混用

    // ❌ 危险:可能造成双重释放
    Widget* raw = new Widget();
    std::shared_ptr<Widget> sp1(raw);
    std::shared_ptr<Widget> sp2(raw);  // 危险!
    
    // ✅ 安全:使用make_shared
    auto sp1 = std::make_shared<Widget>();
    auto sp2 = sp1;  // 共享所有权
    
  3. 不要返回智能指针管理的对象的裸指针

    class ResourceManager {
    private:
        std::shared_ptr<Resource> resource_;
        
    public:
        // ❌ 危险:外部可能delete这个指针
        Resource* getRaw() { return resource_.get(); }
        
        // ✅ 安全:返回shared_ptr
        std::shared_ptr<Resource> getShared() { return resource_; }
        
        // ✅ 安全:返回weak_ptr(观察者模式)
        std::weak_ptr<Resource> getWeak() { return resource_; }
    };
    
  4. 注意线程安全

    // shared_ptr的引用计数操作是原子的,但对象访问不是
    std::shared_ptr<int> sp = std::make_shared<int>(42);
    
    // ❌ 非线程安全:需要额外同步
    // 线程1:*sp = 100;
    // 线程2:*sp = 200;
    
    // ✅ 线程安全:引用计数操作
    // 线程1:auto sp2 = sp;
    // 线程2:sp.reset();
    
  5. 使用enable_shared_from_this

    class Widget : public std::enable_shared_from_this<Widget> {
    public:
        std::shared_ptr<Widget> getShared() {
            return shared_from_this();  // 安全地获取shared_ptr
        }
    };
    
    int main() {
        auto widget = std::make_shared<Widget>();
        auto sp = widget->getShared();  // 安全
        return 0;
    }
    

📊 4. 总结与面试要点

通过本文的学习,我们全面掌握了C++智能指针的核心知识体系:

🎯 核心要点回顾

  1. 智能指针的诞生背景

    • 🚨 传统裸指针管理内存容易导致内存泄漏、双重释放等问题
    • 🔍 智能指针基于RAII(资源获取即初始化)原则,确保资源自动释放
  2. 四大智能指针对比

    • std::unique_ptr:独占所有权,轻量高效,适合大多数场景
    • std::shared_ptr:共享所有权,引用计数管理,适合复杂场景
    • std::weak_ptr:弱引用,解决循环引用问题,观察者模式
    • std::auto_ptr:已弃用,存在所有权转移问题
  3. 关键特性掌握

    • 🔒 shared_ptr的线程安全性:引用计数原子操作安全,但对象访问需额外同步
    • 🔄 循环引用问题:使用weak_ptr打破循环
    • 🛠️ 自定义删除器:管理非内存资源(文件、网络连接等)
    • 📦 数组管理:unique_ptr直接支持,shared_ptr需自定义删除器

💡 面试高频问题

  1. RAII原则是什么?智能指针如何体现RAII?

    • RAII:资源获取即初始化,构造函数获取资源,析构函数释放资源
    • 智能指针在构造时获取内存,析构时自动释放
  2. unique_ptr、shared_ptr、weak_ptr的区别?

    • unique_ptr:独占所有权,不可拷贝,可移动
    • shared_ptr:共享所有权,引用计数,可拷贝
    • weak_ptr:弱引用,不增加引用计数,解决循环引用
  3. shared_ptr的线程安全问题?

    • 引用计数操作是原子的,线程安全
    • 对象访问需要额外同步(如mutex)
  4. 循环引用问题如何解决?

    • 使用weak_ptr替代shared_ptr中的循环引用成员
  5. make_shared和直接new的区别?

    • make_shared:一次内存分配,更高效,异常安全
    • 直接new:两次内存分配(对象和控制块)

🚀 最佳实践总结

  1. 优先选择顺序

    裸指针 → unique_ptr → shared_ptr → weak_ptr
    
  2. 创建方式

    // 推荐
    auto ptr1 = std::make_unique<T>();
    auto ptr2 = std::make_shared<T>();
    
    // 不推荐
    std::shared_ptr<T> ptr3(new T());
    
  3. 所有权管理

    • 明确所有权:谁创建,谁释放
    • 避免混用:不要将裸指针与智能指针混用
    • 使用enable_shared_from_this:在类内部安全获取shared_ptr
  4. 性能考虑

    • unique_ptr几乎无开销,性能接近裸指针
    • shared_ptr有引用计数开销,避免过度使用
    • 小对象、局部变量优先使用unique_ptr

📚 学习资源推荐

  1. 官方文档

  2. 经典书籍

    • 《Effective Modern C++》- Scott Meyers
    • 《C++ Primer》第5版 - Stanley B. Lippman
  3. 实战练习

    • 实现简化版智能指针(理解原理)
    • 在项目中替换裸指针为智能指针
    • 分析现有代码中的内存管理问题

🎖️ 写在最后

智能指针是现代C++内存管理的基石,掌握它不仅能够避免内存泄漏等常见问题,更能写出更加安全、健壮的代码。在实际开发中:

  • 🛡️ 安全第一:优先使用智能指针,减少手动内存管理
  • 🧠 理解原理:不仅要知道怎么用,更要明白为什么这样设计
  • 🔧 因地制宜:根据具体场景选择合适的智能指针
  • 📈 持续优化:定期检查代码中的内存使用,优化资源管理

希望本文能帮助你全面掌握C++智能指针,在面试和实际工作中游刃有余!🚀


📌 温馨提示:本文内容基于C++11及以上标准,建议在实际项目中使用C++14或C++17以获得更好的智能指针支持。

Logo

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

更多推荐