1 序
1.1 智能指针
为了避免手动在堆中分配出的内存没有释放造成内存泄露的问题,C++11提供了智能指针。
智能指针将指针封装成一个栈对象。栈对象在生命周期结束自动销毁时会调用析构函数。智能指针基本上也是在析构函数中做文章,实现的堆上内存管理。
C++11推荐使用的智能指针有unique_ptr
、shared_ptr
和weak_ptr
三种。
1.2 内容简介
本文为std::unique_ptr
的实现篇,介绍实现一个UniquePtr
类,实现与std::unique_ptr
类似的功能。代码实现参考了std::unique_ptr
的实现,当然仅仅是简单的实现,准确的说是实现了std::unique_ptr
的一种特例。
实现UniquePtr
的目的仅仅是为了更直观理解学习std::unique_ptr
的用法和其实现中的亮点,并不是为了替代或者在工程中使用。代码都是需要时间去修改稳定的。大家公认的轮子,特别STL的轮子,直接学习使用就行了,不要自己搞。
1.3 测试环境
-
系统:Windows | 10 ;
-
编译环境:CLion | 2020.1.1 ;
-
编译工具:CMake | 3.16.5;
-
编译器:MinGW GCC | 8.1.0 (x86_64-posix-seh-rev0, Built by MinGW-W64 project) ;
默认情况下gcc编译器会自动优化临时对象构造新对象的行为。为了更好的了解对象的构造流程,需要在CMake中添加如下命令关闭该优化。
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-elide-constructors")
1.4 示例说明
为了便于查看UniquePtr
所托管指针的申请和释放,示例使用下面的自定义Test
类。
class Test {
public:
explicit Test(int a = 0) : a_(a), str_(new char[32]) {
// explicit禁用隐式类型转换
sprintf(str_, "%d", a_);
printf("Test[%p].[%s] constructor\n", this, str_);
}
~Test() {
printf("~Test[%p].[%s]\n", this, str_);
delete[] str_;
}
// ...
public:
int a_;
char *str_;
};
2 std::unique_ptr自定义deleter
std::unique_ptr
支持传入自定义deleter
,在std::unique_ptr
对象释放析构的时候调用会调用该deleter
释放托管指针内存。在不传入deleter
的情况下,默认的deleter
使用delete
释放托管指针内存。
当使用std::unique_ptr
去托管new[]
生成的指针时,需要使用delete[]
去释放托管指针内存。此时,默认的deleter
就不可用,需要传入自定义的deleter
。有了自定义的deleter
,std::unique_ptr
甚至可以托管文件流、套接字(socket)等。
2.1 仿函数方式
托管new[]
的自定义的deleter
可以使用仿函数实现。实现一个仿函数,在其中调用delete[]
。
template<typename T>
struct array_deleter {
void operator()(T *p) {
delete[] p;
}
};
然后,使用如下方式传入array_deleter
。
std::unique_ptr<Test, array_deleter<Test>> t1(new Test[10]);
在t1
析构的时候会自动调用array_deleter
,释放托管指针内存。
2.2 匿名函数方式
托管new[]
的自定义的deleter
可以使用匿名(lambda)函数实现。实现一个匿名函数,在其中调用delete[]
。为了让代码看起来简洁可以将匿名函数存储在一个lambda_deleter
变量中。
auto lambda_deleter = [](Test *p) -> void {
delete[] p;
};
然后,使用如下方式传入lambda_deleter
。
std::unique_ptr<Test, decltype(lambda_deleter)> t2(new Test[10], lambda_deleter);
在t2
析构的时候会自动调用lambda_deleter
,释放托管指针内存。