[C++面试] RAII资源获取即初始化(重点)-优快云博客
一、入门
1、智能指针支持加减吗?为什么?
智能指针(如std::shared_ptr
和std::unique_ptr
)不支持直接的加减操作。这是因为智能指针的设计目标是安全地管理资源所有权,而非像原始指针那样进行内存地址的算术运算。
- 原始指针:允许通过加减操作(如
p + n
)直接访问内存地址,适用于数组遍历等场景。 - 智能指针:专注于资源管理(如自动释放内存),不鼓励直接操作内存地址。加减操作可能破坏其封装性,导致悬空指针或未定义行为。
- 加减操作可能导致智能指针指向非法内存(如已释放的对象)
二、进阶
1、unique_ptr
与五法则的关系
五法则(Rule of Five)是指:当一个类需要自定义析构函数、拷贝构造函数或拷贝赋值运算符时,通常也需要自定义移动构造函数和移动赋值运算符
五法则要素 | unique_ptr 的实现方式 |
---|---|
析构函数 | 默认释放资源,无需自定义。 |
拷贝构造函数 | 显式删除(= delete ),禁止拷贝。 |
拷贝赋值运算符 | 显式删除(= delete ),禁止拷贝。 |
移动构造函数 | 编译器自动生成,正确转移资源所有权。 |
移动赋值运算符 | 编译器自动生成,正确转移资源所有权。 |
std::unique_ptr
是一种独占式智能指针,同一时间只能有一个 std::unique_ptr
指向某个对象。为了保证这种独占性,std::unique_ptr
对拷贝构造函数和赋值运算符进行了限制,具体做法是将它们删除(在 C++ 中,使用 = delete
语法)
template <typename T>
class SimpleUniquePtr {
public:
// 构造函数
explicit SimpleUniquePtr(T* ptr = nullptr) : data(ptr) {}
// 析构函数
~SimpleUniquePtr() {
delete data;
}
// 移动构造函数
SimpleUniquePtr(SimpleUniquePtr&& other) noexcept : data(other.data) {
other.data = nullptr;
}
// 移动赋值运算符
SimpleUniquePtr& operator=(SimpleUniquePtr&& other) noexcept {
if (this != &other) {
delete data;
data = other.data;
other.data = nullptr;
}
return *this;
}
// 删除拷贝构造函数
SimpleUniquePtr(const SimpleUniquePtr& other) = delete;
// 删除赋值运算符
SimpleUniquePtr& operator=(const SimpleUniquePtr& other) = delete;
private:
T* data;
};
SimpleUniquePtr<int> ptr1(new int(42));
// SimpleUniquePtr<int> ptr2 = ptr1; // 错误,拷贝构造函数已删除
// SimpleUniquePtr<int> ptr2(ptr1); // 错误,拷贝构造函数已删除
// SimpleUniquePtr<int> ptr3;
// ptr3 = ptr1; // 错误,赋值运算符已删除
2、智能指针与内存位置的关系
无论智能指针本身位于何处,其管理的对象必须是动态分配的资源(堆内存或需手动释放的资源,如文件句柄)
智能指针理论上可管理栈内存(需自定义删除器阻止释放操作),但违反设计初衷
int x = 10;
auto ptr = std::shared_ptr<int>(&x, [](int*){}); // 自定义空删除器
对象类型 | 存储位置 | 是否需要智能指针 | 示例 |
---|---|---|---|
智能指针管理的对象 | 堆内存 | 必须 | std::make_shared<int>(42) |
智能指针自身 | 栈/堆/静态区 | 通常栈 | std::unique_ptr<int> uPtr(...) |
普通栈对象 | 栈内存 | 不需要 | int x = 10; |
智能指针的主要功能是自动管理动态分配的内存(即堆内存),解决手动new/delete
可能引发的内存泄漏问题。栈内存由编译器自动管理生命周期,无需智能指针干预
- 堆内存分配:通过
new
或std::make_shared
/std::make_unique
动态分配堆内存,智能指针接管其所有权 - 控制块存储:
shared_ptr
的控制块(含引用计数)通常与对象一起分配在堆上,以支持多指针共享
auto ptr = std::make_shared<int>(42); // 对象和控制块均在堆上
智能指针变量ptr
本身在栈上,但其管理的int
对象和引用计数控制块均在堆内存中
// 最常见的用法,利用栈对象自动析构特性释放资源
std::unique_ptr<int> uPtr(new int(10)); // uPtr在栈上
// 智能指针本身可通过new动态分配(但需手动管理其自身生命周期,不推荐)
auto* p = new std::unique_ptr<int>(new int(20));
3、使用 unique_ptr 类实例化一个 Carp 对象,而 Carp 类继承了 Fish 类。将该对象作为 Fish 指针传递时是否会出现切除问题?
会出现切除问题。unique_ptr
是一种独占式智能指针。
当使用unique_ptr
实例化一个Carp
对象,并将其作为Fish
指针传递时,由于unique_ptr
不支持隐式转换(不像shared_ptr
有控制块可以处理类型转换等复杂情况 ),如果直接进行指针转换传递,会导致Carp
对象中特有的成员被 “切除”(即丢失派生类特有的部分 ),因为Fish
指针只能指向Fish
类对象,而无法完整表示Carp
对象。
正确的做法可以是使用std::shared_ptr
等支持多态的智能指针,或者在设计上避免这种直接的指针传递方式。
三、高阶
1、 写时复制机制(Copy on Write,COW)
主要用于读多写少的场景,通过延迟深拷贝操作来提升性能。
初始共享:多个智能指针共享同一份数据,仅持有对原数据的引用(或浅拷贝),避免立即执行深拷贝的开销
触发复制条件:当某个指针尝试对数据进行非只读操作(如修改数据)时,才会触发深拷贝,创建数据副本供修改,其他指针仍共享原数据
通过引用计数跟踪共享数据的指针数量。若引用计数大于1且需修改数据,则执行深拷贝;若引用计数为1,则直接修改原数据。shared_ptr
结合unique()
方法判断是否需要复制。
关键设计:为*
和->
运算符分别提供const
和非const
版本。非const操作触发深拷贝:
if (!data.unique()) { // 判断是否唯一所有者
data = std::make_shared<T>(*data); // 深拷贝
}
// 修改data指向的副本
#include <iostream>
#include <memory>
#include <string>
class CowString {
private:
// 使用 std::shared_ptr 管理字符串数据
std::shared_ptr<std::string> data;
// 复制数据并返回新的 shared_ptr
std::shared_ptr<std::string> copyOnWrite() {
if (data.use_count() > 1) {
// 如果引用计数大于 1,复制一份数据
return std::make_shared<std::string>(*data);
}
return data;
}
public:
// 构造函数
CowString(const std::string& str = "") : data(std::make_shared<std::string>(str)) {}
// 拷贝构造函数
CowString(const CowString& other) : data(other.data) {}
// 赋值运算符
CowString& operator=(const CowString& other) {
if (this != &other) {
data = other.data;
}
return *this;
}
// 获取字符串的常量引用
const std::string& get() const {
return *data;
}
// 修改字符串的某个字符
void setChar(size_t index, char c) {
data = copyOnWrite();
(*data)[index] = c;
}
};
int main() {
CowString str1("Hello");
CowString str2 = str1;
std::cout << "str1: " << str1.get() << std::endl;
std::cout << "str2: " << str2.get() << std::endl;
// 修改 str2 的第一个字符
str2.setChar(0, 'J');
std::cout << "str1: " << str1.get() << std::endl;
std::cout << "str2: " << str2.get() << std::endl;
return 0;
}
四、其他
1、现在C++里不要用new、delete