智能指针相关的常见面试题

智能指针

侯捷面向对象课程中介绍智能指针:

定义:智能指针是存储 指向 动态分配的对象 的指针 的类。它实质上是一个类对象,但表现得像一个指针(指针所能做的操作,这个类也要全部允许)。

image-20240621101340204

1.1 智能指针实现原理

答题方向:从核心概念实现机制两个方向回答,并结合实际应用场景,逐步深入。

  • 先简单介绍什么是智能指针:智能指针是一个类模板,旨在管理动态分配的对象的生命周期,防止内存泄漏。

    ​ 智能指针的原理基于RAII原则(资源获取即初始化)确保资源在合适的时机被释放(构造函数中获取资源,析构函数中释放资源)

  • 再介绍智能指针的分类与使用场景:两种主要的智能指针:std::unique_ptrstd::shared_ptr

    • std::unique_ptr:独占所有权,不允许多个智能指针同时持有同一资源。当std::unique_ptr被销毁时,它会自动释放所拥有的对象。它不能被复制,但可以通过std::move()函数进行所有权转移。(适用于独占资源管理,例如文件句柄)
    • std::shared_ptr:共享所有权,允许多个智能指针共享同一资源,通过引用计数控制资源释放std::shared_ptr可以被复制和赋值。(std::shared_ptr 适用于资源共享,例如对象池。)
    • 注意:weak_ptr严格来说,不能算是“智能指针”,只是一个类的弱引用,避免std::shared_ptr循环引用,不影响资源的生命周期
  • std::unique_ptr 的原理
    • 特点:独占所有权,不可复制
    • 核心机制
      • 包含一个裸指针,使用构造函数初始化,析构函数释放资源。
      • 禁止拷贝构造和拷贝赋值(通过 delete 拷贝语义函数实现)。
      • 支持移动语义,通过移动构造或移动赋值将资源所有权转移到新的 unique_ptr 实例。
//unique_ptr的简单实现
template<typename T>
class UniquePtr{
    //包含一个裸指针
private:
	T* ptr; 
public:
    explicit UniquePtr(T* p = nullptr):ptr(p){};
    ~UniquePtr() {delete ptr};
    //禁止拷贝
    UniquePtr(const UniquePtr&) = delete;
    //禁止赋值
    UniquePtr& operator=(const UniquePtr &) = delete;
    //允许移动拷贝
    UniquePtr(UniquePtr&& other) noexcept {
        this.ptr = other.ptr
        other.ptr = nullptr;
    }
    //允许移动赋值
    UniquePtr& operator=(UniquePtr&& other) noexcept{
        //防止自我赋值
        if(this != other) {
            delete ptr;
            ptr = other.ptr;
            other.ptr = nullptr;
        }
        return *this;
    }
    //智能指针:像指针的类(pointer-like classes),因此需要重载操作符。
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }
};
  • std::shared_ptr 的原理

    • 特点:多个指针共享所有权,通过引用计数控制资源的释放。

    • 核心机制

      • 一个指向管理对象的原始指针 + 控制块指针
      • 引用计数由一个独立的控制块(Control Block)管理,通常包括:
        • 引用计数指针refCount(持有 shared_ptr 的数量)。
        • 弱引用计数(持有 weak_ptr 的数量)。
      • shared_ptr 对象析构时,引用计数减一,为零时释放资源。
      • 使用 std::make_shared 提供高效的内存分配。
template<typename T>
class SharedPtr{
    //包含一个裸指针 和 引用计数
private:
    T* ptr;
    //为什么引用计数是一个指针而非SharedPtr对象的成员?
        //因为引用计数必须是所有 shared_ptr 对象之间共享的,指针可以让它们共享同一个计数器。
        //如果引用计数是一个普通的成员变量(非指针),每个 shared_ptr 实例都会有自己的独立计数器,这样无法正确反映资源的共享情况
    size_t* refCount;
public:
    explicit SharedPtr(T* p = nullptr): ptr(p), refCount(new size_t(1)){};
    ~SharedPtr() {
        if(--(*refCount) == 0) {
            delete ptr;
            delete refCount;
        }
    }
    //拷贝构造
    SharedPtr(const SharedPtr& other):ptr(other.ptr), refCount(other.refCount) {
        (*refCount)++;
    }
    //赋值构造
    SharedPtr& operator=(SharedPtr& other) {
        if(&other == this) return *this;//自我赋值保护
        //释放自我资源
        //赋值操作会让当前对象放弃对原资源的所有权,并接管新对象的资源。如果不正确释放当前资源,会导致 资源泄漏 或 引用计数错误。
        if (--(*refCount) == 0) {   //如果当前对象管理的资源仍有其他 SharedPtr 对象在共享(引用计数 > 1),则仅减少引用计数。
                                    //如果当前对象是最后一个管理该资源的 SharedPtr 对象(引用计数变为 0),则释放资源。
            delete ptr;
            delete refCount;
        }
        this->ptr = other.ptr;
        this->refCount = other.refCount;
        (*refCount)++;
        return *this;
    }
    //智能指针
    T& operator*() const { return *ptr; }
    T* operator->() const { return ptr; }
};

1.2 智能指针,里面的计数器何时会改变?

增加:(1)拷贝构造(2)赋值运算(3)函数参数按值传递或返回

std::shared_ptr<int> sp1 = std::make_shared<int>(10); 	// 创建时引用计数 = 1
// 拷贝构造
std::shared_ptr<int> sp2(sp1); 							// sp2 共享资源,引用计数 = 2
// 赋值运算符
std::shared_ptr<int> sp3;
sp3 = sp1; // sp2 共享资源,引用计数 = 3
// 函数参数按值传递
void func(std::shared_ptr<int> sp) {
    // ...
}
func(sp1); // 引用计数 = 4(在函数内部增加,函数返回后恢复)

减少:(1)智能指针销毁(2)赋值给新资源(3)函数按值返回时临时对象销毁。

// 智能指针销毁
{
    std::shared_ptr<int> sp = std::make_shared<int>(10); // 引用计数 = 1
} // sp 超出作用域,引用计数 = 0,资源被释放
// 赋值给新资源:sp1 -> sp2
std::shared_ptr<int> sp1 = std::make_shared<int>(10); // 引用计数 = 1
std::shared_ptr<int> sp2 = std::make_shared<int>(20); // 引用计数 = 1
sp1 = sp2; // sp1 原资源引用计数 = 0,资源释放;sp2 资源引用计数 = 2
// 函数按值返回传递
std::shared_ptr<int> createResource() {
    return std::make_shared<int>(10); // 临时对象引用计数 = 1
}
std::shared_ptr<int> sp = createResource(); // 临时对象销毁,sp 引用计数 = 1

永不为0:若两个对象通过 std::shared_ptr 相互引用,会导致循环引用问题,引用计数无法降到 0,资源无法释放。

class A {
public:
    std::shared_ptr<B> b_ptr;
    A() { std::cout << "A constructor" << std::endl; }
    ~A() { std::cout << "A destructor" << std::endl; }
};
class B {
public:
    std::shared_ptr<A> a_ptr;
    B() { std::cout << "B constructor" << std::endl; }
    ~B() { std::cout << "B destructor" << std::endl; }
};
int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    
    a->b_ptr = b; //循环引用,引用计数器永远无法到达0,即(如:当a要析构时,发现还有指针(b->a_ptr)指向自己,则引用计数器不会为0,所有a析构时只会将refCount--;
    b->a_ptr = a;
}

解决办法:用 std::weak_ptr 打破循环引用。

class A {
public:
    std::shared_ptr<B> b_ptr;
    A() { std::cout << "A constructor" << std::endl; }
    ~A() { std::cout << "A destructor" << std::endl; }
};
class B {
public:
    std::weak_ptr<A> a_weak_ptr;  // 使用 weak_ptr
    B() { std::cout << "B constructor" << std::endl; }
    ~B() { std::cout << "B destructor" << std::endl; }
};
int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    
    a->b_ptr = b;
    b->a_weak_ptr = a;  // 使用 weak_ptr
}

1.3 智能指针和管理的对象分别在哪个区?

  • 智能指针

    • 存储位置智能指针对象本身通常分配在栈上(但也可以位于堆/静态区),利用栈对象超出生命周期后自动析构的特征,无需手动delete释放资源。
      • 栈上std::shared_ptr<int> sp = std::make_shared<int>(42);
        • 智能指本身sp位于栈上,但是控制块引用计数refCount、管理的对象ptr(42)都位于堆上。
  • 智能指针管理的对象

    • 存储位置:智能指针管理的对象通常位于堆上
    • 分配方式:智能指针负责分配和释放资源。
      • std::make_shared 会在堆上分配控制块和对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值