MST

星途 面试题库

面试题:C++ 中智能指针与引用计数机制及应用场景

请深入讲解 C++ 中智能指针(如 unique_ptr、shared_ptr、weak_ptr)的实现原理,以及引用计数机制在其中的应用。并举例说明在复杂的内存管理场景(如循环引用、对象生命周期管理等)下,如何合理地使用智能指针来避免内存泄漏和悬空指针问题。
25.9万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

1. 智能指针实现原理

unique_ptr

  • 原理unique_ptr 采用了一种独占式拥有的策略,它持有对对象的唯一所有权。当 unique_ptr 被销毁时,它所指向的对象也会被自动销毁。这是通过在 unique_ptr 内部维护一个指向动态分配对象的指针,并在 unique_ptr 的析构函数中释放该对象来实现的。
  • 实现示例(简化)
template <typename T>
class MyUniquePtr {
    T* ptr;
public:
    MyUniquePtr(T* p = nullptr) : ptr(p) {}
    ~MyUniquePtr() {
        if (ptr) {
            delete ptr;
        }
    }
    T& operator*() { return *ptr; }
    T* operator->() { return ptr; }
    // 移动构造函数和移动赋值运算符
    MyUniquePtr(MyUniquePtr&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr;
    }
    MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
        if (this != &other) {
            delete ptr;
            ptr = other.ptr;
            other.ptr = nullptr;
        }
        return *this;
    }
    // 禁止拷贝构造函数和拷贝赋值运算符
    MyUniquePtr(const MyUniquePtr&) = delete;
    MyUniquePtr& operator=(const MyUniquePtr&) = delete;
};

shared_ptr

  • 原理shared_ptr 采用引用计数机制来管理对象的生命周期。多个 shared_ptr 可以指向同一个对象,当指向该对象的最后一个 shared_ptr 被销毁时,对象才会被释放。它内部包含两个指针,一个指向对象,另一个指向一个控制块,控制块中包含引用计数以及可能的其他信息(如删除器)。
  • 实现示例(简化)
template <typename T>
class MySharedPtr {
    T* ptr;
    long* refCount;
public:
    MySharedPtr(T* p = nullptr) : ptr(p) {
        if (ptr) {
            refCount = new long(1);
        } else {
            refCount = new long(0);
        }
    }
    MySharedPtr(const MySharedPtr& other) : ptr(other.ptr), refCount(other.refCount) {
        ++(*refCount);
    }
    MySharedPtr& operator=(const MySharedPtr& other) {
        if (this != &other) {
            release();
            ptr = other.ptr;
            refCount = other.refCount;
            ++(*refCount);
        }
        return *this;
    }
    ~MySharedPtr() {
        release();
    }
    T& operator*() { return *ptr; }
    T* operator->() { return ptr; }
private:
    void release() {
        if (--(*refCount) == 0) {
            delete ptr;
            delete refCount;
        }
    }
};

weak_ptr

  • 原理weak_ptr 是一种弱引用,它不增加对象的引用计数,主要用于解决 shared_ptr 中的循环引用问题。weak_ptr 可以指向由 shared_ptr 管理的对象,但它不会影响对象的生命周期。通过 weak_ptr 可以获取一个 shared_ptr,如果对象已经被销毁,获取的 shared_ptr 将为空。
  • 实现示例(简化)
template <typename T>
class MyWeakPtr {
    T* ptr;
    long* refCount;
public:
    MyWeakPtr() : ptr(nullptr), refCount(nullptr) {}
    MyWeakPtr(const MySharedPtr<T>& sharedPtr) : ptr(sharedPtr.ptr), refCount(sharedPtr.refCount) {}
    MySharedPtr<T> lock() const {
        if (ptr && refCount && *refCount > 0) {
            return MySharedPtr<T>(*this);
        }
        return MySharedPtr<T>();
    }
};

2. 引用计数机制在智能指针中的应用

  • shared_ptr:引用计数是 shared_ptr 的核心机制。每次创建一个新的 shared_ptr 指向对象时,引用计数加1;当 shared_ptr 被销毁或赋值给另一个对象时,引用计数减1。当引用计数变为0时,对象被释放。
  • weak_ptr:虽然 weak_ptr 本身不直接参与引用计数,但它可以观察 shared_ptr 管理的对象的引用计数。通过 lock 方法,weak_ptr 可以尝试获取一个 shared_ptr,如果对象的引用计数大于0,则获取成功,否则获取失败(返回空的 shared_ptr)。

3. 在复杂内存管理场景下合理使用智能指针

循环引用

  • 问题描述:当两个或多个对象通过 shared_ptr 相互引用时,会形成循环引用,导致引用计数永远不会为0,从而造成内存泄漏。
  • 解决方法:使用 weak_ptr 打破循环引用。
  • 示例
class B;
class A {
public:
    std::shared_ptr<B> ptrB;
    ~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
    std::weak_ptr<A> ptrA;
    ~B() { std::cout << "B destroyed" << std::endl; }
};
int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->ptrB = b;
    b->ptrA = a;
    // 此时 a 和 b 不会被销毁,因为存在循环引用
    // 但如果 B 中的 ptrA 是 weak_ptr,就可以打破循环
    return 0;
}

当离开 main 函数作用域时,如果 B 中的 ptrAweak_ptr,那么 ab 的引用计数会正常减少,最终对象 AB 会被正确销毁。

对象生命周期管理

  • 问题描述:在复杂的系统中,对象之间的依赖关系复杂,手动管理对象的生命周期容易出错,导致内存泄漏或悬空指针。
  • 解决方法:使用智能指针来自动管理对象的生命周期。例如,在函数返回对象时,可以返回 unique_ptrshared_ptr,确保对象在不再需要时被正确释放。
  • 示例
std::unique_ptr<int> createInt() {
    return std::make_unique<int>(42);
}
int main() {
    auto ptr = createInt();
    // 当 ptr 离开作用域时,它所指向的 int 对象会被自动释放
    return 0;
}

在这个例子中,createInt 函数返回一个 unique_ptr<int>,调用者 main 函数接收这个 unique_ptr,当 ptr 离开 main 函数作用域时,int 对象会被自动释放,避免了内存泄漏。同时,由于 unique_ptr 的独占性,也避免了悬空指针问题(因为不存在其他指针指向该对象)。如果需要多个地方共享这个对象,可以使用 shared_ptr