MST
星途 面试题库

面试题:C++智能指针在预防内存泄漏中的应用与原理

阐述C++中不同类型智能指针(std::unique_ptr、std::shared_ptr、std::weak_ptr)的特点和适用场景,以及它们是如何预防内存泄漏的,结合具体代码示例说明智能指针在复杂对象关系(如对象之间存在循环引用)下的处理方式。
13.0万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

std::unique_ptr

  1. 特点
    • 独占式拥有对象,同一时刻只能有一个 std::unique_ptr 指向给定对象。
    • 不能复制,只能移动,移动语义高效,避免了不必要的资源复制。
    • 析构时自动释放所管理的对象。
  2. 适用场景
    • 当一个对象在程序的某个特定部分有明确的单一所有者时使用,比如函数内部创建的临时对象。
    • 管理动态分配的数组可以使用 std::unique_ptr<T[]>
  3. 预防内存泄漏方式
    • 超出作用域时,std::unique_ptr 的析构函数自动调用 delete 释放对象,无需手动管理内存。

示例代码:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass created" << std::endl; }
    ~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};

void testUniquePtr() {
    std::unique_ptr<MyClass> ptr(new MyClass());
    // 这里无需手动delete ptr,ptr离开作用域时会自动释放MyClass对象
}

int main() {
    testUniquePtr();
    return 0;
}

std::shared_ptr

  1. 特点
    • 允许多个 std::shared_ptr 指向同一个对象,通过引用计数来管理对象的生命周期。
    • 支持复制和赋值操作,每次复制或赋值,引用计数增加;std::shared_ptr 析构时,引用计数减少。
    • 当引用计数为0时,自动释放所管理的对象。
  2. 适用场景
    • 当多个对象需要共享同一资源时使用,比如在多个模块间共享数据。
  3. 预防内存泄漏方式
    • 引用计数机制确保只有当所有指向对象的 std::shared_ptr 都销毁后,对象才会被释放,避免了手动管理不当导致的内存泄漏。

示例代码:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass created" << std::endl; }
    ~MyClass() { std::cout << "MyClass destroyed" << std::endl; }
};

void testSharedPtr() {
    std::shared_ptr<MyClass> ptr1(new MyClass());
    std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加
    {
        std::shared_ptr<MyClass> ptr3 = ptr1; // 引用计数再增加
    } // ptr3离开作用域,引用计数减少
    // 当ptr1和ptr2都离开作用域时,MyClass对象才会被释放
}

int main() {
    testSharedPtr();
    return 0;
}

std::weak_ptr

  1. 特点
    • 弱引用指针,指向由 std::shared_ptr 管理的对象,但不增加引用计数。
    • 主要用于解决 std::shared_ptr 的循环引用问题。
    • 可以通过 lock() 方法尝试获取指向对象的 std::shared_ptr,如果对象已被释放,lock() 返回一个空的 std::shared_ptr
  2. 适用场景
    • 在对象之间存在循环引用时使用,打破循环引用,避免内存泄漏。
  3. 预防内存泄漏方式
    • 不增加引用计数,当循环引用中的对象只被 std::weak_ptr 指向时,不会阻止对象被释放,从而打破循环引用,预防内存泄漏。

示例代码:

#include <iostream>
#include <memory>

class B;

class A {
public:
    std::weak_ptr<B> bPtr;
    ~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
    std::weak_ptr<A> aPtr;
    ~B() { std::cout << "B destroyed" << std::endl; }
};

void testWeakPtr() {
    std::shared_ptr<A> a(new A());
    std::shared_ptr<B> b(new B());
    a->bPtr = b;
    b->aPtr = a;
    // 这里不会出现循环引用导致的内存泄漏
}

int main() {
    testWeakPtr();
    return 0;
}

复杂对象关系(循环引用)处理方式

在使用 std::shared_ptr 时,如果对象之间存在循环引用,会导致引用计数永远不会为0,从而造成内存泄漏。例如:

#include <iostream>
#include <memory>

class B;

class A {
public:
    std::shared_ptr<B> bPtr;
    ~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
    std::shared_ptr<A> aPtr;
    ~B() { std::cout << "B destroyed" << std::endl; }
};

void testLoopReference() {
    std::shared_ptr<A> a(new A());
    std::shared_ptr<B> b(new B());
    a->bPtr = b;
    b->aPtr = a;
    // a和b的引用计数都不会变为0,A和B对象不会被释放,造成内存泄漏
}

int main() {
    testLoopReference();
    return 0;
}

解决循环引用问题可以使用 std::weak_ptr,将其中一个类的成员指针改为 std::weak_ptr,如上述 testWeakPtr 示例代码,这样就打破了循环引用,对象能够正常释放,避免内存泄漏。