汉字魔法师
118.67M · 2026-02-04
以下知识和代码来自Light-City/CPlusPlusThings: C++那些事 (github.com)
void test(bool isEmpty)
{
int* p=new int();
if(isEmpty) return;
...
delete p;
}
如上面的代码所示如果isEmpty为true,程序就会提前返回,p所指向的内存就无被delete。随着使用程序时间增加,内存占用会越来越大。因此手动管理指针可能会带来问题,而智能指针就是为了解决这个问题诞生的。
智能指针本质上就是利用RALL(资源获取即初始化)原理来实现的,通过析构函数在超出对象作用域后自动释放指针内存。
template<typename T>
class unique_ptr {
public:
explicit unique_ptr(
T* ptr = nullptr) noexcept
: ptr_(ptr) {
}
~unique_ptr() noexcept {
delete ptr_;
}
...
};
我们来看如下代码
#include <iostream>
class RawPointerWrapper {
public:
int* data;
RawPointerWrapper(int value) {
data = new int(value);
std::cout << "Resource allocated at: " << data << std::endl;
}
~RawPointerWrapper() {
std::cout << "Deleting resource at: " << data << std::endl;
delete data;
}
};
int main() {
RawPointerWrapper w1(10);
RawPointerWrapper w2 = w1;
return 0;
}
运行后结果为:
Resource allocated at: 00000228FCD95F70
Deleting resource at: 00000228FCD95F70
Deleting resource at: 00000228FCD95F70
代码中c++编译器默认为类添加了“浅拷贝”版本的拷贝构造函数。因此w1在赋值给w2时只是讲指针地址给了w2,w2和w1指向了同一块内存,程序会对同一块地址delete两次,这会导致程序的崩溃。而unique_ptr就能避免这种情况,它是一种“独占”型指针,意味着同一时间只能有一个unique_ptr指向一块内存地址,在我们的实现代码中,就需要禁止拷贝构造函数和拷贝赋值运算符。
#include<iostream>
template<typename T>
class unique_ptr {
public:
explicit unique_ptr(
T* ptr = nullptr) noexcept
: ptr_(ptr) {
}
~unique_ptr() noexcept {
delete ptr_;
}
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
...
};
禁用了“拷贝”后,当涉及指针的交换,我们就要“移动”指针,并将被移动的指针置空。这时移动构造函数和移动赋值运算符就登场了。
unique_ptr(unique_ptr&& other) noexcept {
ptr_ = other.release();
}
// copy and swap 始终只有一个对象有管理这块空间的权限
unique_ptr& operator=(unique_ptr rhs) noexcept {
rhs.swap(*this);
return *this;
}
// 原来的指针释放所有权
T* release() noexcept {
T* ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
void swap(unique_ptr& rhs) noexcept {
using std::swap;
swap(ptr_, rhs.ptr_); // 转移指针所有权
}
到这里我们的unique_ptr就差不多完成了,以下是完整代码。
#include<iostream>
template<typename T>
class unique_ptr {
public:
explicit unique_ptr(
T* ptr = nullptr) noexcept
: ptr_(ptr) {
}
~unique_ptr() noexcept {
delete ptr_;
}
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
operator bool() const noexcept { return ptr_; }
T* get() const noexcept { return ptr_; }
unique_ptr(unique_ptr&& other) noexcept {
ptr_ = other.release();
}
// copy and swap 始终只有一个对象有管理这块空间的权限
unique_ptr& operator=(unique_ptr rhs) noexcept {
rhs.swap(*this);
return *this;
}
// 原来的指针释放所有权
T* release() noexcept {
T* ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
void swap(unique_ptr& rhs) noexcept {
using std::swap;
swap(ptr_, rhs.ptr_); // 转移指针所有权
}
private:
T* ptr_;
};
template<typename T>
void swap(unique_ptr<T>& lhs, unique_ptr<T>& rhs) {
lhs.swap(rhs);
}
int main() {
unique_ptr<int> ptr(new int(5));
return 0;
}
template<typename T>
void swap(unique_ptr<T>& lhs, unique_ptr<T>& rhs) {
lhs.swap(rhs);
}
在我们的代码中我们写了一个全局模板的swap,有人可能会觉得这多此一举。但如果你不写这个函数,当我们定义两个unqiue_ptr指针并使用swap交换时,swap函数并不知道我们的unique_ptr内部长什么样,它就会用最保险的方式即套用通用公式。
// std::swap 的通用逻辑
T temp = std::move(ptr1);
ptr1 = std::move(ptr2);
ptr2 = std::move(temp);
此时会涉及三次移动。如果加上我们的全局模板的swap,编译器就会通过ADL机制找到我们的全局模板的swap,最后通过lhs.swap(rhs)调用我们类中的成员函数swap直接交换两个指针地址从而避免三次移动,使我们代码效率更高。