[C++面试] RAII资源获取即初始化(重点)-CSDN博客
智能指针是一种类模板,用于管理动态分配的内存,能在对象生命周期结束时自动释放内存,避免内存泄漏。在传统的 C++ 中,使用 new
分配内存后,必须手动使用 delete
释放,若忘记或在异常情况下未执行 delete
,就会导致内存泄漏。而智能指针利用 C++ 的 RAII(资源获取即初始化)技术,在对象构造时获取资源,在析构时释放资源,大大提高了代码的安全性。
#include
#include
int main() {
std::unique_ptr ptr(new int(10));
std::cout << *ptr << std::endl;
// 当 ptr 离开作用域时,内存会自动释放
return 0;
}
std::unique_ptr
是什么,有什么特点? std::unique_ptr
是一种独占式智能指针,同一时间只能有一个 std::unique_ptr
指向某个对象。它不允许拷贝构造和赋值操作,但可以通过 std::move
转移所有权。
#include
#include
int main() {
std::unique_ptr ptr1(new int(20));
std::unique_ptr p1 = std::make_unique(42);
// std::unique_ptr ptr2 = ptr1; // 错误,不允许拷贝
std::unique_ptr ptr2 = std::move(ptr1); // 转移所有权
if (!ptr1) {
std::cout << "ptr1 不再拥有对象" << std::endl;
}
if (ptr2) {
std::cout << *ptr2 << std::endl;
}
return 0;
}
避免跨线程传递 std::unique_ptr(所有权转移需明确)
std::unique_ptr
不允许拷贝构造和赋值操作,只能通过 std::move
来转移所有权。在跨线程传递时,如果没有正确使用 std::move
,会导致编译错误;如果在多个线程中同时持有对同一个对象的 std::unique_ptr
,会导致对象被多次释放,产生未定义行为。
unique_ptr
:独占所有权,适用于单一所有者场景(如工厂模式返回对象)shared_ptr
:共享所有权,适用于多个对象需要共享同一资源(如缓存系统)weak_ptr
:观察者模式,解决shared_ptr
循环引用问题(如双向链表节点)std::shared_ptr
是什么,如何工作?std::shared_ptr
是一种共享式智能指针,多个 std::shared_ptr
可以指向同一个对象。它使用引用计数来管理对象的生命周期,每增加一个指向该对象的 std::shared_ptr
,引用计数加 1;每减少一个指向该对象的 std::shared_ptr
,引用计数减 1。当引用计数变为 0 时,对象被自动释放。
#include
#include
int main() {
std::shared_ptr ptr1(new int(30));
std::cout << "引用计数: " << ptr1.use_count() << std::endl;
std::shared_ptr ptr2 = ptr1;
std::cout << "引用计数: " << ptr1.use_count() << std::endl;
return 0;
}
std::weak_ptr
有什么作用,它和 std::shared_ptr
有什么关系?std::weak_ptr
是一种弱引用智能指针,它不拥有对象的所有权,不会增加对象的引用计数。它主要用于解决 std::shared_ptr
可能出现的循环引用问题。当 std::shared_ptr
之间存在循环引用时,引用计数永远不会变为 0,导致内存泄漏。
可以从 std::shared_ptr
或另一个 std::weak_ptr
构造,通过 lock()
方法可以获得一个 std::shared_ptr
来访问对象
#include
#include
class B;
class A {
public:
std::shared_ptr b_ptr;
~A() { std::cout << "A 析构" << std::endl; }
};
class B {
public:
std::weak_ptr a_ptr; // 使用 std::weak_ptr 打破循环引用
~B() { std::cout << "B 析构" << std::endl; }
};
int main() {
std::shared_ptr a = std::make_shared();
std::shared_ptr b = std::make_shared();
a->b_ptr = b;
b->a_ptr = a;
return 0;
}
#include
#include
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
int main() {
std::shared_ptr ptr(&MyClass());
ptr->doSomething();
return 0;
}
临时对象生命周期问题:MyClass()
创建了一个临时对象,其生命周期在完整表达式 std::shared_ptr
结束时就终止了。当这个临时对象被销毁后,ptr
就变成了一个悬空指针,后续调用 ptr->doSomething();
会导致未定义行为。
双重释放风险:std::shared_ptr
会在其生命周期结束时尝试释放所管理的对象。由于临时对象在完整表达式结束时已经被销毁,当 ptr
析构时再次尝试释放该对象,会造成双重释放,这也是未定义行为。
void func(std::shared_ptr p1, std::shared_ptr p2) {
// 函数体
}
func(std::shared_ptr(new int(1)), std::shared_ptr(new int(2)));
如果 new int(2) 抛出异常,而 new int(1) 已经成功分配内存但还未被 std::shared_ptr 完全接管,那么这块内存就会泄漏。
std::make_shared
:在 C++11 标准中被引入,作为一种更安全、高效的方式来创建std::shared_ptr
对象。
std::make_unique
:在 C++14 标准中被引入,用于更安全、高效地创建std::unique_ptr
对象。#include
class MyClass {}; int main() { auto ptr = std::make_unique (); return 0; }
通过控制块(包含引用计数、弱计数等)管理资源,拷贝时递增计数,析构时递减
线程安全:引用计数操作是原子性的,但指向的对象访问需要额外同步(如互斥锁)
智能指针允许自定义删除器,当对象的生命周期结束时,会调用自定义的删除器来释放资源。自定义删除器可以是一个函数、函数对象或 lambda 表达式。
#include
#include
// 自定义删除器
void custom_deleter(int* ptr) {
std::cout << "使用自定义删除器释放内存" << std::endl;
delete ptr;
}
int main() {
std::unique_ptr ptr(new int(40), custom_deleter);
return 0;
}
auto deleter = [](FILE* f) { fclose(f); };
std::unique_ptr fp(fopen("test.txt", "r"), deleter);
std::shared_ptr
的引用计数操作是线程安全的,但对象的访问和修改不一定是线程安全的。
如果多个线程同时访问和修改同一个对象,需要使用同步机制(如互斥锁)来保证线程安全。
在传递智能指针时,要避免出现数据竞争和悬空指针的问题。
#include
#include
#include
#include
std::shared_ptr shared_data;
std::mutex mtx;
void modify_shared_data() {
std::lock_guard lock(mtx);
if (shared_data) {
*shared_data = 50;
}
}
int main() {
shared_data = std::make_shared(0);
std::thread t(modify_shared_data);
t.join();
std::cout << *shared_data << std::endl;
return 0;
}
shared_ptr
int* raw = new int(10);
std::shared_ptr p1(raw);
std::shared_ptr p2(raw);
会导致重复释放。应使用p2 = p1
或make_shared
weak_ptr
的lock()
方法返回什么?如何处理失效情况?为了安全地访问 std::weak_ptr
所指向的对象,需要调用 lock()
方法。lock()
方法会尝试创建一个 std::shared_ptr
来管理 std::weak_ptr
所指向的对象。如果对象已经被销毁,lock()
会返回一个空的 std::shared_ptr
;否则,会返回一个有效的 std::shared_ptr
,并且对象的引用计数会增加。
#include
#include
int main() {
std::shared_ptr shared = std::make_shared(42);
std::weak_ptr weak = shared;
// 使用 lock() 检查有效性
if (auto locked = weak.lock()) {
std::cout << "Value: " << *locked << std::endl;
} else {
std::cout << "Object has been destroyed." << std::endl;
}
// 释放 shared_ptr,对象被销毁
shared.reset();
// 再次检查
if (auto locked = weak.lock()) {
std::cout << "Value: " << *locked << std::endl;
} else {
std::cout << "Object has been destroyed." << std::endl;
}
return 0;
}
std::enable_shared_from_this
的作用是什么?std::enable_shared_from_this
是一个模板类,用于在类的成员函数内部安全地获取指向当前对象的 std::shared_ptr
。
#include
#include
class BadExample {
public:
std::shared_ptr getShared() {
return std::shared_ptr(this); // 错误做法
}
~BadExample() {
std::cout << "BadExample destroyed" << std::endl;
}
};
int main() {
std::shared_ptr ptr1 = std::make_shared();
std::shared_ptr ptr2 = ptr1->getShared();
// 程序结束时,ptr1 和 ptr2 会分别尝试释放对象,导致重复释放
return 0;
}
std::enable_shared_from_this
就像是一个协调者,它能让新创建的 std::shared_ptr
和原来的 std::shared_ptr
共享同一个引用计数。
在类的成员函数中,如果直接使用 this
指针创建 std::shared_ptr
,会导致创建一个新的、独立的 std::shared_ptr
,这会使同一个对象有多个独立的引用计数,从而可能导致资源的重复释放。而 std::enable_shared_from_this
提供了一个 shared_from_this()
成员函数,该函数会返回一个指向当前对象的 std::shared_ptr
,并且这个 std::shared_ptr
会与已有的 std::shared_ptr
共享同一个引用计数。
#include
#include
class MyClass : public std::enable_shared_from_this {
public:
std::shared_ptr getShared() {
return shared_from_this();
}
void doSomething() {
std::cout << "Doing something..." << std::endl;
}
};
int main() {
std::shared_ptr shared = std::make_shared();
std::shared_ptr anotherShared = shared->getShared();
anotherShared->doSomething();
return 0;
}
shared_ptr
共享同一个对象。#include
template
class SimpleSharedPtr {
public:
// 构造函数
explicit SimpleSharedPtr(T* ptr = nullptr) : data(ptr), ref_count(new int(1)) {}
// 拷贝构造函数
SimpleSharedPtr(const SimpleSharedPtr& other) : data(other.data), ref_count(other.ref_count) {
++(*ref_count);
}
// 赋值运算符
SimpleSharedPtr& operator=(const SimpleSharedPtr& other) {
if (this != &other) {
if (--(*ref_count) == 0) {
delete data;
delete ref_count;
}
data = other.data;
ref_count = other.ref_count;
++(*ref_count);
}
return *this;
}
// 析构函数
~SimpleSharedPtr() {
if (--(*ref_count) == 0) {
delete data;
delete ref_count;
}
}
// 重载解引用运算符
T& operator*() const {
return *data;
}
// 重载箭头运算符
T* operator->() const {
return data;
}
private:
T* data;
int* ref_count;
};
// 测试代码
class MyClass {
public:
MyClass() { std::cout << "MyClass constructor" << std::endl; }
~MyClass() { std::cout << "MyClass destructor" << std::endl; }
void doSomething() { std::cout << "Doing something..." << std::endl; }
};
int main() {
SimpleSharedPtr ptr1(new MyClass());
SimpleSharedPtr ptr2 = ptr1;
ptr2->doSomething();
return 0;
}
在多线程环境中,指向引用计数的指针需要考虑锁或者使用原子操作。因为多个线程可能同时对引用计数进行读写操作,如果不进行同步,会导致数据竞争,从而使引用计数的值出现错误,最终可能导致资源泄漏或重复释放等问题。
#include
#include
template
class ThreadSafeSharedPtr {
public:
explicit ThreadSafeSharedPtr(T* ptr = nullptr) : data(ptr), ref_count(new std::atomic(1)) {}
// 拷贝构造函数等操作需要正确处理原子引用计数
// ...
private:
T* data;
std::atomic* ref_count;
};