MST
星途 面试题库

面试题:C++ 动态内存分配与智能指针及多线程场景

在多线程环境下,使用C++ 动态内存分配并结合智能指针(如std::unique_ptr、std::shared_ptr),请描述可能出现的问题及解决方案。假设存在一个复杂的对象层次结构,对象之间存在循环引用,在多线程场景下如何正确地管理这些对象的动态内存,避免内存泄漏和数据竞争?请给出详细的分析及代码示例。
17.6万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

可能出现的问题

  1. 数据竞争:多个线程同时访问和修改动态分配的内存,可能导致未定义行为。例如,一个线程正在释放内存,而另一个线程正在访问该内存。
  2. 内存泄漏:在复杂对象层次结构存在循环引用时,使用std::shared_ptr可能导致内存泄漏。因为循环引用会使引用计数永远不会降为0,从而无法释放内存。

解决方案

  1. 数据竞争
    • 使用互斥锁(std::mutex)来保护共享资源。在访问动态分配的内存前,锁定互斥锁,访问结束后解锁。
    • 使用std::lock_guardstd::unique_lock来自动管理互斥锁的锁定和解锁,避免手动管理可能出现的错误。
  2. 内存泄漏(针对循环引用)
    • 使用std::weak_ptr来打破循环引用。std::weak_ptr不增加引用计数,它可以观察std::shared_ptr管理的对象,当std::shared_ptr管理的对象被释放时,std::weak_ptr会变为空。

详细分析及代码示例

#include <iostream>
#include <memory>
#include <mutex>

class B;

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

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

// 模拟循环引用导致内存泄漏
void testCycleLeak() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->b = b;
    b->a = a;
    // 这里a和b不会被释放,因为循环引用导致引用计数不会降为0
}

class C;

class D {
public:
    std::weak_ptr<C> c;
    ~D() { std::cout << "D destroyed" << std::endl; }
};

class C {
public:
    std::shared_ptr<D> d;
    ~C() { std::cout << "C destroyed" << std::endl; }
};

// 使用std::weak_ptr打破循环引用
void testNoCycleLeak() {
    std::shared_ptr<C> c = std::make_shared<C>();
    std::shared_ptr<D> d = std::make_shared<D>();
    c->d = d;
    d->c = c;
    // 这里c和d会被正确释放,因为std::weak_ptr不增加引用计数
}

std::mutex mtx;

// 多线程数据竞争示例及解决方案
class E {
public:
    int data;
    E(int value) : data(value) {}
};

void threadFunction(std::shared_ptr<E>& sharedE) {
    std::lock_guard<std::mutex> lock(mtx);
    // 访问和修改共享资源
    sharedE->data++;
}

void testDataRace() {
    std::shared_ptr<E> sharedE = std::make_shared<E>(0);
    std::thread t1(threadFunction, std::ref(sharedE));
    std::thread t2(threadFunction, std::ref(sharedE));
    t1.join();
    t2.join();
    std::cout << "Final data value: " << sharedE->data << std::endl;
}

int main() {
    testCycleLeak();
    testNoCycleLeak();
    testDataRace();
    return 0;
}

在上述代码中:

  • testCycleLeak函数展示了循环引用导致内存泄漏的情况。
  • testNoCycleLeak函数通过std::weak_ptr打破循环引用,确保对象正确释放。
  • testDataRace函数展示了多线程环境下的数据竞争问题,并通过std::mutexstd::lock_guard来解决。