MST

星途 面试题库

面试题:C++ 智能指针与传统指针初始化防野指针对比

请详细阐述C++ 中智能指针(如std::unique_ptr、std::shared_ptr等)是如何解决野指针问题的,与传统指针初始化策略相比,智能指针在资源管理和避免野指针方面有哪些优势和局限性?
15.6万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

智能指针解决野指针问题的方式

  1. std::unique_ptr
    • 原理std::unique_ptr 采用独占式拥有资源的策略。当 std::unique_ptr 对象被销毁(例如离开其作用域)时,它会自动释放其所指向的资源。
    • 避免野指针:由于 std::unique_ptr 对资源具有唯一所有权,不存在多个指针指向同一资源的情况。当 std::unique_ptr 析构时,资源被释放,不会留下悬空的指针,从而避免了野指针的产生。例如:
#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> ptr(new int(10));
    // 当ptr离开作用域时,指向的int对象会被自动释放
    return 0;
}
  1. std::shared_ptr
    • 原理std::shared_ptr 采用引用计数的方式管理资源。每个 std::shared_ptr 对象都维护一个指向资源的指针以及一个引用计数。当一个新的 std::shared_ptr 对象指向该资源时,引用计数增加;当一个 std::shared_ptr 对象销毁时,引用计数减少。当引用计数变为 0 时,资源被释放。
    • 避免野指针:只要有 std::shared_ptr 对象存在,资源就不会被释放。当所有指向资源的 std::shared_ptr 对象都销毁后,资源自动释放,不会产生野指针。例如:
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr1(new int(20));
    std::shared_ptr<int> ptr2 = ptr1;
    // ptr1和ptr2都指向同一个资源,引用计数为2
    return 0;
    // 当ptr1和ptr2离开作用域,引用计数降为0,资源被释放
}

智能指针在资源管理和避免野指针方面的优势

  1. 自动资源释放:智能指针通过析构函数自动释放资源,无需手动调用 delete,减少了忘记释放资源导致的内存泄漏问题,同时也避免了因手动释放后忘记将指针置为 nullptr 而产生的野指针问题。
  2. 异常安全:在传统指针使用中,如果在分配资源后、释放资源前抛出异常,可能导致资源无法释放。智能指针由于其自动释放机制,在异常发生时能确保资源正确释放,保证了程序的异常安全性。例如:
void traditionalPointerFunction() {
    int* ptr = new int(30);
    // 可能抛出异常的代码
    if (someCondition()) {
        throw std::exception();
    }
    delete ptr;
}

void smartPointerFunction() {
    std::unique_ptr<int> ptr(new int(30));
    // 可能抛出异常的代码
    if (someCondition()) {
        throw std::exception();
    }
    // 即使抛出异常,ptr离开作用域时资源也会自动释放
}
  1. 简化代码:智能指针使代码更简洁,不需要显式地编写资源释放逻辑,提高了代码的可读性和可维护性。

智能指针的局限性

  1. 性能开销std::shared_ptr 的引用计数机制需要额外的内存开销来存储引用计数,并且每次引用计数的增加和减少都需要原子操作,这在高并发环境下可能会带来性能问题。std::unique_ptr 虽然没有引用计数开销,但相比普通指针,它仍然有一定的封装开销。
  2. 循环引用问题:在使用 std::shared_ptr 时,如果存在循环引用(例如两个对象互相持有对方的 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;
    }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->bPtr = b;
    b->aPtr = a;
    // a和b的引用计数都不会降为0,A和B的资源不会释放
    return 0;
}

解决循环引用问题通常需要使用 std::weak_ptr,但这增加了代码的复杂性。 3. 兼容性:在一些老的代码库或者对内存使用有严格要求的嵌入式系统等场景中,智能指针可能不被支持或者使用起来受到限制,因为它们依赖于标准库,可能需要额外的运行时支持。