一. 智能指针

为了更容易,更安全地使用动态内存,标准库提供了两种智能指针类型来管理动态对象. 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 就会直接析构其管理的资源

引用计数的存在对性能会产生部分影响

  1. shared_ptr 是原生指针的两倍,因为它们内部除了包含一个指向资源的原生指针之外,同时还包含了指向资源引用计数的原生指针
  2. 引用计数的内存必须被动态分配
  3. 引用计数的递增或递减必须是原子操作

创建 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]