面试手撕------智能指针
通常在面试中需要手撕实现的是shared_ptr,其主要特性是使用一个计数器进行对资源的管理。在面试中只需要简单要实现的以下功能:
- 引用计数:通过计数器跟踪指针被引用的次数
- 自动内存管理:当引用计数归零时自动释放内存
- 拷贝语义:支持深拷贝,增加引用计数
- 移动语义:支持资源所有权的转移
- 异常安全:移动操作使用noexcept保证安全性
- 操作符重载:支持常规指针的*和->操作
1、成员变量
也就是说在其中需要两个成员变量,一个是需要存储的指针,另一个是一个计数器。
那么计数器的类型是什么?
首先,该指向同一个资源的计数器值需要相同,可以同步对计数器的增加或减少。截至到这时有两种方式
1、指针
2、static。
那么他们有什么区别?static在泛型编程中每一个类型都共享这个变量,而同一个类型不一定只有一个资源。所以static不可行。也就是说是这样的
template <typename T>
class SmartPoint
{
private:
T* prt;
size_t* count;
};
2、计数器减少
由于拷贝构造和析构函数都有可能对计数器经行减少删除,所以将这一步封装成一个新的函数(记得是私有函数)。
void release() {
if(--(*count) == 0)
{
delete ptr;
delete count;
ptr = nullptr;
count == nullptr;
std::cout << "资源已经释放" << std::endl;
}
}
3、构造函数、析构函数
通常在智能指针构造函数中,我们都是传入一个new出来的指针交由智能指针管理或是不传参。且我们不希望指针在传入时出现隐式类型转换,所以使用explicit关键字
explicit SmartPoint(T* p = nullptr) :ptr(p),count(new size_t(1))
{
std::cout << "创建智能指针" << endl;
}
~SmartPoint()
{
if (count)release();
}
4、拷贝构造函数、拷贝赋值运算符
在这两个中要注意的就是为了防止两个成员变量发生改变要使用const,以及拷贝赋值运算符需要返回*this(见Effective C++条款10:令operator=返回一个reference to *this)和this在拷贝赋值前需要先删除原先资源。
SmartPoint(const SmartPoint<T>& other)
:ptr(other.ptr), count(other.count)
{
++(*count);
std::cout << "拷贝构造智能指针" << (*count) << std::endl;
}
SmartPoint<T>& operator=(const SmartPoint<T>& other)
{
if (this != &other)
{
release();
ptr = other.ptr;
count = other.count;
++(*count);
std::cout << "拷贝赋值,计数" << *count << std::endl;
}
return *this;
}
5、移动拷贝构造函数、移动拷贝赋值运算符
移动拷贝是对资源的直接移动,不会让计数器发生增加或者减少,因为传入的是右值,只需要将资源转移就行。
要注意的是容器重新分配时,使用移动而不是拷贝,前提是移动操作是noexcept的,否则会回退到拷贝,影响性能。
SmartPoint(SmartPoint<T>&& other) noexcept
:ptr(other.ptr), count(other.count)
{
other.ptr = nullptr;
other.count = nullptr;
}
SmartPoint<T>& operator=(SmartPoint<T>&& other) noexcept
{
if (this != &other)
{
release();
ptr = other.ptr;
count = other.count;
other.ptr = nullptr;
other.count = nullptr;
}
return *this;
}
6、重载操作符
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
size_t use_count() const { return count ? *count : 0; }
7、测试
// 示例使用
int main() {
SmartPoint<int> p1(new int(42)); // 计数 1
{
SmartPoint<int> p2 = p1; // 拷贝构造,计数 2
SmartPoint<int> p3;
p3 = p2; // 拷贝赋值,计数 3
std::cout << "数值: " << *p3 << "\n";
} // p2/p3 析构,计数 1
SmartPoint<int> p4 = std::move(p1); // 移动构造
std::cout << "移动后计数: " << p4.use_count() << "\n";
return 0;
}