面试题答案
一键面试可能出现的问题
- 数据竞争:多个线程同时访问和修改动态分配的内存,可能导致未定义行为。例如,一个线程正在释放内存,而另一个线程正在访问该内存。
- 内存泄漏:在复杂对象层次结构存在循环引用时,使用
std::shared_ptr
可能导致内存泄漏。因为循环引用会使引用计数永远不会降为0,从而无法释放内存。
解决方案
- 数据竞争:
- 使用互斥锁(
std::mutex
)来保护共享资源。在访问动态分配的内存前,锁定互斥锁,访问结束后解锁。 - 使用
std::lock_guard
或std::unique_lock
来自动管理互斥锁的锁定和解锁,避免手动管理可能出现的错误。
- 使用互斥锁(
- 内存泄漏(针对循环引用):
- 使用
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::mutex
和std::lock_guard
来解决。