MST

星途 面试题库

面试题:C++指针使用的高级难度问题

在C++中,智能指针(如std::unique_ptr、std::shared_ptr和std::weak_ptr)是如何管理动态分配的内存的?请详细说明它们的工作原理,并解释何时应该使用每种智能指针。如果在多线程环境下使用智能指针,可能会遇到哪些问题,如何解决?
14.3万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

std::unique_ptr

  1. 工作原理
    • std::unique_ptr 采用独占式所有权模型。它持有对动态分配对象的唯一所有权,当 std::unique_ptr 本身被销毁(例如离开其作用域)时,它所指向的对象会自动被释放。
    • 它不能被拷贝,只能被移动。移动语义使得 std::unique_ptr 可以在不同的作用域之间转移所有权。例如:
    std::unique_ptr<int> ptr1(new int(42));
    std::unique_ptr<int> ptr2 = std::move(ptr1); // ptr1 失去所有权,ptr2 获得所有权
    
  2. 适用场景
    • 当你希望某个对象在某个特定的作用域内存在,并且该对象的所有权不应被共享时,使用 std::unique_ptr。比如一个函数内部创建的临时对象,不需要在函数外部共享其所有权。

std::shared_ptr

  1. 工作原理
    • std::shared_ptr 采用引用计数的方式来管理动态分配的内存。多个 std::shared_ptr 可以指向同一个对象,对象的引用计数会记录有多少个 std::shared_ptr 指向它。
    • 当一个 std::shared_ptr 被创建并指向一个新对象时,引用计数初始化为1。每当有新的 std::shared_ptr 指向该对象(例如通过拷贝构造函数或赋值操作符),引用计数加1。当一个 std::shared_ptr 被销毁(离开作用域或被显式重置),引用计数减1。当引用计数降为0时,对象的内存会被自动释放。
    • 例如:
    std::shared_ptr<int> ptr1(new int(42));
    std::shared_ptr<int> ptr2 = ptr1; // 引用计数从1变为2
    ptr1.reset(); // 引用计数从2变为1
    ptr2.reset(); // 引用计数从1变为0,对象内存被释放
    
  2. 适用场景
    • 当需要在多个地方共享对象的所有权时,使用 std::shared_ptr。比如在多个函数或模块之间需要访问同一个对象的场景。

std::weak_ptr

  1. 工作原理
    • std::weak_ptr 是一种弱引用,它指向由 std::shared_ptr 管理的对象,但不会增加对象的引用计数。它主要用于解决 std::shared_ptr 可能出现的循环引用问题。
    • std::weak_ptr 可以通过 lock() 方法尝试获取一个 std::shared_ptr。如果对象仍然存在(即对应的 std::shared_ptr 的引用计数不为0),lock() 方法会返回一个有效的 std::shared_ptr,否则返回一个空的 std::shared_ptr
    • 例如:
    std::shared_ptr<int> ptr1(new int(42));
    std::weak_ptr<int> weakPtr = ptr1;
    std::shared_ptr<int> ptr3 = weakPtr.lock(); // ptr3 有效,指向同一个对象
    ptr1.reset();
    ptr3 = weakPtr.lock(); // ptr3 为空,对象已被释放
    
  2. 适用场景
    • 当存在可能的循环引用问题时,使用 std::weak_ptr 打破循环。比如在双向链表等数据结构中,一个节点持有指向另一个节点的 std::weak_ptr,以避免循环引用导致内存泄漏。

多线程环境下的问题及解决

  1. 问题
    • 引用计数竞争:在多线程环境下,多个线程同时操作 std::shared_ptr 可能导致引用计数的竞争条件。例如,一个线程增加引用计数,而另一个线程同时减少引用计数,可能导致未定义行为。
    • 对象访问竞争:多个线程可能同时通过 std::shared_ptr 访问对象,可能导致数据竞争问题,特别是在对象的成员函数不是线程安全的情况下。
  2. 解决方法
    • 使用互斥锁:可以使用 std::mutex 来保护对 std::shared_ptr 的操作。例如,在修改 std::shared_ptr(如赋值、重置等)之前加锁,操作完成后解锁。
    std::mutex sharedPtrMutex;
    std::shared_ptr<int> sharedData;
    void threadFunction() {
        std::lock_guard<std::mutex> lock(sharedPtrMutex);
        // 安全地操作 sharedData
        sharedData.reset(new int(42));
    }
    
    • 使用 std::atomic_shared_ptr(C++17 及以后)std::atomic_shared_ptr 提供了原子操作语义,使得对 std::shared_ptr 的某些操作(如赋值、比较和交换等)是线程安全的,无需手动加锁。
    std::atomic_shared_ptr<int> atomicSharedData;
    void threadFunction() {
        atomicSharedData.store(std::make_shared<int>(42));
    }
    
    • 对象自身线程安全设计:确保对象的成员函数是线程安全的,或者在访问对象成员时使用合适的同步机制,以避免数据竞争。