1. 面试场景下的shared_ptr实现要点
当面试官要求手写shared_ptr时,核心考察的是对智能指针机制的理解深度和编码基本功。不同于生产环境的完整实现,面试场景下需要把握"关键部分完整,辅助功能精简"的原则。以下是典型实现需要覆盖的核心模块:
1.1 基础框架结构
cpp复制template<typename T>
class SharedPtr {
private:
T* ptr; // 原始指针
int* ref_count; // 引用计数器
public:
// 构造函数/析构函数
// 拷贝控制成员
// 核心接口
};
这个骨架展示了三个关键要素:
- 存储的原始指针(ptr)
- 堆上分配的引用计数器(ref_count)
- 必要的成员函数声明
注意:引用计数器必须使用指针形式在堆上分配,这样才能被多个SharedPtr实例共享。栈上的计数器会导致每个智能指针实例拥有独立的计数。
1.2 引用计数机制实现
引用计数是shared_ptr的核心机制,需要完整实现:
cpp复制void add_ref() {
if(ref_count) ++(*ref_count);
}
void release() {
if(ref_count && --(*ref_count) == 0) {
delete ptr;
delete ref_count;
ptr = nullptr;
ref_count = nullptr;
}
}
这段代码体现了两个关键点:
add_ref()在每次拷贝构造或赋值时增加计数release()在每次析构或重置时减少计数,并在计数归零时释放资源
1.3 必要接口实现
面试中必须实现的基础接口包括:
cpp复制// 解引用操作符
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
// 获取原始指针
T* get() const { return ptr; }
// 重置功能
void reset(T* new_ptr = nullptr) {
release();
if(new_ptr) {
ptr = new_ptr;
ref_count = new int(1);
}
}
2. 面试官期待的完整度边界
2.1 必须实现的完整功能
-
构造/析构函数组:
cpp复制explicit SharedPtr(T* p = nullptr) : ptr(p), ref_count(p ? new int(1) : nullptr) {} ~SharedPtr() { release(); } SharedPtr(const SharedPtr& other) : ptr(other.ptr), ref_count(other.ref_count) { add_ref(); } -
拷贝赋值运算符:
cpp复制SharedPtr& operator=(const SharedPtr& other) { if(this != &other) { release(); ptr = other.ptr; ref_count = other.ref_count; add_ref(); } return *this; } -
移动语义(加分项):
cpp复制SharedPtr(SharedPtr&& other) noexcept : ptr(other.ptr), ref_count(other.ref_count) { other.ptr = nullptr; other.ref_count = nullptr; }
2.2 可以简化的辅助功能
以下功能在面试中通常可以简要处理或省略:
- 自定义删除器(除非特别要求)
- 类型转换函数(static_pointer_cast等)
- 原子操作版本的引用计数
- 异常安全处理细节
- 完整的移动赋值运算符实现
3. 实现中的关键陷阱与避坑指南
3.1 引用计数器的初始化
常见错误是忘记为引用计数器分配内存:
cpp复制// 错误示例
SharedPtr(T* p = nullptr) : ptr(p), ref_count(p ? 1 : 0) {}
// 正确应为 new int(1)
3.2 自赋值检查
赋值运算符必须处理自赋值情况:
cpp复制SharedPtr& operator=(const SharedPtr& other) {
if(this == &other) return *this; // 关键检查
// ...其余实现
}
3.3 空指针处理
所有操作都应考虑ptr为nullptr的情况:
cpp复制T& operator*() const {
if(!ptr) throw std::runtime_error("dereference nullptr");
return *ptr;
}
4. 面试进阶:可能追问的扩展点
4.1 线程安全实现
如果面试官追问线程安全,可以这样扩展:
cpp复制void add_ref() {
if(ref_count) __sync_fetch_and_add(ref_count, 1);
}
void release() {
if(ref_count && __sync_sub_and_fetch(ref_count, 1) == 0) {
delete ptr;
delete ref_count;
}
}
4.2 弱指针支持
讨论weak_ptr的实现思路:
- 引入weak_count计数器
- 控制块分离设计
- 循环引用解决方案
4.3 性能优化方向
- 控制块内存布局优化
- 无锁引用计数实现
- 类型擦除技术应用
5. 手写实现完整示例代码
以下是面试场景下足够完整的实现:
cpp复制template<typename T>
class SharedPtr {
private:
T* ptr;
int* ref_count;
void add_ref() {
if(ref_count) ++(*ref_count);
}
void release() {
if(ref_count && --(*ref_count) == 0) {
delete ptr;
delete ref_count;
}
}
public:
explicit SharedPtr(T* p = nullptr) : ptr(p), ref_count(p ? new int(1) : nullptr) {}
~SharedPtr() { release(); }
SharedPtr(const SharedPtr& other) : ptr(other.ptr), ref_count(other.ref_count) {
add_ref();
}
SharedPtr& operator=(const SharedPtr& other) {
if(this != &other) {
release();
ptr = other.ptr;
ref_count = other.ref_count;
add_ref();
}
return *this;
}
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
T* get() const { return ptr; }
void reset(T* p = nullptr) {
release();
if(p) {
ptr = p;
ref_count = new int(1);
}
}
int use_count() const { return ref_count ? *ref_count : 0; }
explicit operator bool() const { return ptr != nullptr; }
};
6. 面试策略与评估标准
6.1 面试官评估维度
-
核心机制完整性(60%):
- 引用计数实现是否正确
- 资源释放逻辑是否完备
- 拷贝控制成员是否齐全
-
边界情况处理(30%):
- 空指针处理
- 自赋值检查
- 异常安全考虑
-
代码风格与设计(10%):
- 模板使用规范
- 接口设计合理性
- 代码可读性
6.2 回答策略建议
- 先写出基础框架,再逐步完善
- 对每个函数解释设计意图
- 主动讨论可能的优化方向
- 诚实说明简化部分的原因
在实际面试中,我曾见过候选人因为过度追求完整实现而陷入困境。建议先确保基础功能正确,再根据时间情况扩展高级特性。一个正确的基础实现比有缺陷的"完整"实现得分更高。
