一. 智能指针
为了更容易,更安全地使用动态内存,标准库提供了两种智能指针类型来管理动态对象. unique_ptr 和 shared_ptr
智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象. shared_ptr 允许多个指针指向同一个对象,unique_ptr 则“独占”所指向的对象
1.1 unique_ptr
当你要使用智能指针时,首先要想到的应该是 unique_ptr,它用来管理独占资源
任一时刻,只能有 一个 unique_ptr 指向给定对象,当 unique_ptr 被销毁,它所管理的资源会自动被销毁
创建 unique_ptr 指针
#include <cstdio>
#include <memory>
int main(void){
std::unique_ptr<int> ptr_int_1 = std::make_unique<int>(999);
auto ptr_int_2 = std::make_unique<int>(1024);
printf("%d\n", *ptr_int_1);
printf("%d\n", *ptr_int_2);
return 0;
}
unique_ptr 表达的是“独占”语义,move 一个 unique_ptr 会将资源所有权从源智能指针转向目的指针,同时将源智能指针的资源置为 null
#include <cstdio>
#include <memory>
int main(void){
auto ptr_int_1 = std::make_unique<int>(999);
auto ptr_int_2 = std::move(ptr_int_1);
printf("%d\n", *ptr_int_2);
// 报错!!!
printf("%d\n", *ptr_int_1);
return 0;
}
#include <cstdio>
#include <memory>
int main(void){
auto ptr_int_1 = std::make_unique<int>(999);
auto ptr_int_2 = std::make_unique<int>(666);
printf("ptr_int_1: %d\n", *ptr_int_1);
ptr_int_1 = std::move(ptr_int_2);
printf("ptr_int_1: %d\n", *ptr_int_1);
return 0;
}
unique_ptr 不允许拷贝,即禁用了拷贝构造函数和拷贝赋值运算符。假如真的可以拷贝 unique_ptr,那么将会有两个 unique_ptr 指向同一块资源,每一个都认为自己拥有且可销毁那块资源,这与它的“独占”语义矛盾。因此 unique_ptr 是一个 move_only 类型
#include <cstdio>
#include <memory>
// 报错 !!!
int main(void){
auto ptr_int_1 = std::make_unique<int>(999);
auto ptr_int_2 = ptr_int_1;
return 0;
}
nullptr 的使用,置空智能指针所管理的资源,并将智能指针置空
#include <cstdio>
#include <memory>
int main(void){
auto ptr_int_1 = std::make_unique<int>(999);
auto ptr_int_1 = nullptr;
return 0;
}
返回智能指针中保存的裸指针
#include <cstdio>
#include <memory>
int main(void){
auto ptr_int_1 = std::make_unique<int>(999);
printf("ptr_int_1: %d\n", *ptr_int_1.get());
return 0;
}
重置智能指针内的资源
#include <cstdio>
#include <memory>
int main(void){
auto ptr_int_1 = std::make_unique<int>(999);
printf("ptr_int_1: %d\n", *ptr_int_1);
int *ptr_int_2 = new int{666};
ptr_int_1.reset(ptr_int_2);
printf("ptr_int_1: %d\n", *ptr_int_1);
return 0;
}
交换两个智能指针所管理的资源
#include <cstdio>
#include <memory>
#include <algorithm>
int main(void){
auto ptr_int_1 = std::make_unique<int>(999);
auto ptr_int_2 = std::make_unique<int>(666);
printf("ptr_int_1: %d\n", *ptr_int_1);
printf("ptr_int_2: %d\n", *ptr_int_2);
std::swap(ptr_int_1, ptr_int_2);
printf("ptr_int_1: %d\n", *ptr_int_1);
printf("ptr_int_2: %d\n", *ptr_int_2);
return 0;
}
1.2 shared_ptr
shared_ptr 用来管理共享资源,其内部使用引用计数,它记录有多少个 shared_ptr 管理该资源,shared_ptr 的构造函数通常会自动递增这个计数,析构函数会自动递减这个计数,而拷贝赋值会两者都做。如果 shared_ptr 在递减后发现引用计数变成了 0,这就说明已经没有其它 shared_ptr 在指向这个资源了,shared_ptr 就会直接析构其管理的资源
引用计数的存在对性能会产生部分影响
- shared_ptr 是原生指针的两倍,因为它们内部除了包含一个指向资源的原生指针之外,同时还包含了指向资源引用计数的原生指针
- 引用计数的内存必须被动态分配
- 引用计数的递增或递减必须是原子操作
创建 shared_ptr 指针
#include <cstdio>
#include <memory>
int main(void){
auto ptr_int_1 = std::make_shared<int>(666);
auto ptr_int_2 = ptr_int_1;
printf("ptr_int_1: %d\n", *ptr_int_1);
printf("ptr_int_2: %d\n", *ptr_int_2);
return 0;
}
移动构造函数是一个特例,从一个 shared_ptr 移动构造另一个 shared_ptr,会使得源 shared_ptr 指向 null,意味着新的 shared_ptr 取代老的 shared_ptr 来指向原来的资源,所以就不需要再修改引用计数了
#include <cstdio>
#include <memory>
int main(void){
auto ptr_int_1 = std::make_shared<int>(666);
auto ptr_int_2 = std::move(ptr_int_1);
printf("ptr_int_2: %d\n", *ptr_int_2);
// 报错!!!
printf("ptr_int_1: %d\n", *ptr_int_1);
return 0;
}
shared_ptr 的 reset 成员函数重置资源时,将源引用计数 -1,新引用计数 + 1
返回引用计数
#include <cstdio>
#include <memory>
int main(void){
auto ptr_int_1 = std::make_shared<int>(999);
auto ptr_int_2 = ptr_int_1;
printf("use_count: %ld\n", ptr_int_1.use_count());
return 0;
}
1.3 unique_ptr 转换为 shared_ptr
转换条件:待转换的 unique_ptr 必须为右值
shared_ptr 中有一个构造函数,可接管右值的 unique_ptr 资源
#include <cstdio>
#include <memory>
std::unique_ptr<int> test(void){
return std::make_unique<int>(999);
}
int main(void){
std::shared_ptr<int> ptr_int_1 = test();
printf("ptr_int_1: %d\n", *ptr_int_1);
return 0;
}
作业
给定一个大小为 n 的整数数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。
示例 1:输入:nums = [3,2,3]
输出:[3]
示例 2:输入:nums = [1]
输出:[1]
示例 3:输入:nums = [1,2]
输出:[1,2]