C++智能指针

C++ 智能指针面试点

1. C++智能指针有哪些?

C++主要有三种智能指针(位于头文件memory中)

  1. std::unique_ptr
    • 不能被复制,只能移动(std::move)。
    • 适用于独占资源管理(如文件、网络连接)。
    • std::make_unique创建(C++14)
  2. std::shared_ptr
    • 采用引用计数,多个shared_ptr共享同一个对象,最后一个销毁时释放资源
    • 存在循环引用风险,可配个std::weak_ptr解决。
  3. std::weak_ptr
    • 依赖shared_ptr,不会增加引用计数。
    • 用于解决shared_ptr循环引用问题。
    • 通过lock()获取shared_ptr,判断对象是否仍然有效。

2. std::shared_ptr原理是什么?实现shared_ptr

分析

  • 每个shared_ptr实例指向同一个对象,并共享一个计数器。
  • 当创建shared_ptr时,计数器初始值为1,拷贝现有shared_ptr时,计数器会+1。
  • shared_ptr被销毁(例如通过析构函数)或重置(通过reset方法)时,计数器会-1。
  • 当计数器为0时,对象被自动删除。

代码

#include <iostream>
using namespace std;

template <typename T>
class shared_count {
private:
    T*     m_ptr;       //指向管理对象
    int    m_count;     //引用计数
public:
    shared_count(T* p = nullptr) : m_ptr(p), m_count(1) {}
    
    shared_count(const shared_count&) = delete;             //禁止拷贝构造
    shared_count& operator=(const shared_count&) = delete;  //禁止拷贝赋值
    ~shared_count() { delete m_ptr; }

    //增加引用计数
    void increment() {
        m_count++;
    }
    //减少引用计数
    void decrement() {
        if (--m_count == 0) {
            delete this;
        }
    }
    //返回引用计数的数值 需要加上const
    int use_count() const {
        return m_count;
    }
};

template <typename T>
class shared_ptr {
private:
    T*                m_ptr;
    shared_count<T>*  m_count;
public:
    shared_ptr(T* p = nullptr) : m_ptr(p) {
        if (p) {
            m_count = new shared_count(p);
        }
        /*
        else                // 没意义的操作,主要有两点
            p = nullptr;    // 1.假设进去else分支,此时p本身就为nullptr,p = nullptr操作无意义
        */     				// 2.p是形参,修改p的值并不会影响调用方传过来的原始指针
    }

    ~shared_ptr() {
        if (m_count) {
            m_count->decrement();
        }
    }

    //拷贝构造时,引用+1
    shared_ptr(const shared_ptr& rhs) : m_ptr(rhs.m_ptr), m_count(rhs.m_count) {
        if (rhs.m_ptr) {
            m_count->increment();
        }
    }
    //移动构造时,转移资源所有权
    shared_ptr(shared_ptr&& rhs) : m_ptr(rhs.m_ptr), m_count(rhs.m_count) {
        rhs.m_ptr = nullptr;
        rhs.m_count = nullptr;
    }

    /*
        reset的用法一定要记住
        细节处理reset参数和不带参数
    */
    void reset(T* ptr = nullptr) {
        if (ptr != m_ptr) {
            if (m_count) {
                m_count->decrement();
            }
        }
    
        m_ptr = ptr;
        if (m_ptr) {
            m_count = new shared_count(ptr);
        }
    }

    T& operator*() const {
        return *m_ptr;
    }

    T* operator->() const {
        return m_ptr;
    }

    T* get() const {
        return m_ptr;
    }

    int use_count() const {
        return m_count->use_count();
    }

};

int main() {
    shared_ptr<int> ptr1(new int(10));
    cout << "value: " << *ptr1 << ", count: " << ptr1.use_count() << endl;

    shared_ptr<int> ptr2 = ptr1;
    cout << "value: " << *ptr2 << ", count: " << ptr2.use_count() << endl;

    shared_ptr<int> ptr3 = move(ptr2);
    cout << "value: " << *ptr3 << ", count: " << ptr3.use_count() << endl;

    ptr3.reset();
    cout << "value: " << *ptr1 << ", count: " << ptr1.use_count() << endl;
    cout << "RawPtr: " << ptr1.get() << endl;

    return 0;
}

make_shared相比new + shared_ptr的好处?

1. 内存分配效率

1)new + shared_ptr的方式创建智能指针时,内存分配分为两步

  • new分配对象的内存
  • shared_ptr构造函数分配的控制块内存

2)make_shared的内存分配只有一步

  • 将对象和控制块的存储空间合并为一次进行内存分配
  • 被管理的对象和内存时连续的,减少了内存分配的开销

所以从make_shared由于内存分配只有一步,此时对象和控制块位于同一片内存,有访问速度快的优势。

2. 异常安全

1)new + shared_ptr非异常安全,存在内存泄漏的风险
当函数参数使用new时,可能因参数求值顺序导致内存泄漏。例如:

func(std::shared_ptr<T>(new T), dowork());

由于函数参数的执行顺序是不确定的,有可能按照如下顺序执行new T成功,dowork()抛出异常,而shared_ptr尚未构造完成,此时最开始的new T分配的内存不会被释放。

2)make_shared的解决方案

func(std::make_shared<T>(), dowork);

make_shared在单步内完成对象的创建和智能指针的初始化,确保安全。

posted @ 2025-04-03 22:10  ydqun  阅读(41)  评论(0)    收藏  举报