智能指针的行为类似常规指针,重要的区别是它负责自动释放所指的对象。C++11标准库提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则"独占"所指向的对象。

C++11提供了三种智能指针:std::shared_ptr, std::unique_ptr, std::weak_ptr,使用时需添加头文件<memory>

一、unique_ptr

unique_ptr <>是c ++ 11提供的智能指针实现之一,用于防止内存泄漏。unique_ptr对象包含一个原始指针,并负责其生命周期。当这个对象被销毁的时候,它的析构函数会删除关联的原始指针。

unique_ptr有重载的- >和*运算符。

1、初始化

直接使用new

unique_ptr<int> up1(new int());      //okay,直接初始化

unique_ptr<int> up2 = new int();    //error! 构造函数是explicit  (C++中的explicit关键字只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显式的, 而非隐式的)

unique_ptr<int> up3(up1);             //error! 不允许拷贝

与shared_ptr不同,unique_ptr拥有它所指向的对象,在某一时刻,只能有一个unique_ptr指向特定的对象。当unique_ptr被销毁时,它所指向的对象也会被销毁。因此不允许多个unique_ptr指向同一个对象,所以不允许拷贝与赋值。

2、成员函数

unique_ptr<T> up 
空的unique_ptr,可以指向类型为T的对象,默认使用delete来释放内存

up.release() up 放弃对它所指对象的控制权,并返回保存的指针,将up置为空,不会释放内存

up.reset(…) 参数可以为 空、内置指针,先将up所指对象释放,然后重置up的值.

用法请见:

https://blog.csdn.net/lijinqi1987/article/details/79005794

https://www.cnblogs.com/DswCnblog/p/5628195.html

 使用实例如下:

#include <iostream>
#include <memory>
 
struct Task {
  int mId;
  Task(int id) : mId(id) {
    std::cout << "Task::Constructor" << std::endl;
  }
  ~Task() {
    std::cout << "Task::Destructor" << std::endl;
  }
};
 
int main() {
  //空unique_ptr对象
  std::unique_ptr<int> ptr1;
 
  //检查unique_ptr对象是否为空
  if (!ptr1) {
    std::cout << "ptr1 is empty" << std::endl;
  }
 
  //检查unique_ptr对象是否为空
  if (ptr1 == nullptr) {
    std::cout << "ptr1 is empty" << std::endl;
  }
 
  //不能通过赋值初始化创建unique_ptr对象
  //std::unique_ptr<Task> taskPtr2 = new Task(); //编译错误
 
  //通过原始指针创建unique_ptr对象
  std::unique_ptr<Task> taskPtr(new Task(23));
 
  //检查taskPtr是否为空,或者是否有关联的原始指针
  if (taskPtr != nullptr) {
    std::cout << "taskPtr is  not empty" << std::endl;
  }
 
  //通过unique_ptr访问内部元素
  std::cout << taskPtr->mId << std::endl;
 
  std::cout << "Reset the taskPtr" << std::endl;
  //重置unique_ptr将删除关联的原始指针,并使unique_ptr对象为空
  taskPtr.reset();
 
  //检查taskPtr是否为空,或者是否有关联的原始指针
  if (taskPtr == nullptr) {
    std::cout << "taskPtr is  empty" << std::endl;
  }
 
  //通过原始指针创建unique_ptr对象
  std::unique_ptr<Task> taskPtr2(new Task(55));
 
  if (taskPtr2 != nullptr) {
    std::cout << "taskPtr2 is  not empty" << std::endl;
  }
 
  //unique_ptr 对象不可复制
  //taskPtr = taskPtr2;  //编译错误
 
  //unique_ptr 对象不可复制
  //std::unique_ptr<Task> taskPtr3 = taskPtr2; //编译错误
 
  {
    //转移所有权
    std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);  //将原始指针的所有权转移给taskPtr4后,taskPtr2将为空
 
    if (taskPtr2 == nullptr) {
      std::cout << "taskPtr2 is  empty" << std::endl;
    }
 
    //taskPtr2的所有权转移给了task4
    if (taskPtr4 != nullptr) {
      std::cout << "taskPtr4 is not empty" << std::endl;
    }
 
    std::cout << taskPtr4->mId << std::endl;
    //taskPtr4超出范围并删除关联的原始指针
  }
 
  //通过原始指针创建unique_ptr对象
  std::unique_ptr<Task>taskPtr5(new Task(55));
 
  if (taskPtr5 != nullptr) {
    std::cout << "taskPtr5 is not empty" << std::endl;
  }
 
  //从原始指针释放对象的所有权
  Task* ptr = taskPtr5.release();
 
  if (taskPtr5 == nullptr) {
    std::cout << "taskPtr5 is empty" << std::endl;
  }
 
  std::cout << ptr->mId << std::endl;
 
  delete ptr;
 
  return 0;
}

运行结果如下:

3、 unique_ptr特性用法——release()和reset()用法区别

//release()用法
 //release()返回原来智能指针指向的指针,只负责转移控制权,不负责释放内存,常见的用法
 unique_ptr<int> q(p.release()) // 此时p失去了原来的的控制权交由q,同时p指向nullptr  
 //所以如果单独用:
 p.release()
 //则会导致p丢了控制权的同时,原来的内存得不到释放

 

//reset()用法
 p.reset()     // 释放p原来的对象,并将其置为nullptr,
 p = nullptr   // 等同于上面一步
 p.reset(q)    // 注意此处q为一个内置指针,令p释放原来的内存,p新指向这个对象

主意release()只转移控制权,并不释放内存,而reset和=nullptr操作会释放原来的内存

 

二、shared_ptr

shared_ptr会记录有多少个shared_ptr指向同一个对象,当我们拷贝或者赋值一个shared_ptr时,计数器加一,被销毁则减一,为0则释放内存(析构函数)。

每个 shared_ptr 对象在内部指向两个内存位置:
1)、指向对象的指针。
2)、用于控制引用计数数据的指针。
共享所有权如何在参考计数的帮助下工作:
1)、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
2)、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。

 1、初始化

1) 构造函数初始化

  std::shared_ptr<int> pointer(new int(1));

  std::shared_ptr<int> pointer1 = pointer;

  std::shared_ptr<std::string> ss(new std::string("AAA"));

  std::shared_ptr<std::string> = std::shared_ptr<std::string>(new std::string("AAA"));

2)std::make_shared 初始化(推荐方式)

  std::shared_ptr<string> p3 = std::make_shared<string>();

  std::shared_ptr<string> p2 = std::make_shared<string>("hello");

  //auto关键字代替std::shared_ptr,p5指向一个动态分配的空vector<string>
  auto p5 = make_shared<vector<string>>();

3)reset初始化

     std::shared_ptr<int> pointer = nullptr;

  pointer.reset(new int(1));

     

2、成员函数 

1) reset()  使 shared_ptr 对象取消与相关指针的关联

不带参:

pointer .reset();    //它将引用计数减少1,如果引用计数变为0,则删除指针。

带参数:

pointer.reset(new int(1));  //在这种情况下,它将在内部指向新指针,因此其引用计数将再次变为1。

使用nullptr重置:

p1 = nullptr;

 2)use_count 引用计数

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    std::shared_ptr<int> ptrA1 = std::make_shared<int>(10);
     std::cout << ptrA1.use_count() << std::endl;
    std:shared_ptr<int> ptrA2(ptrA1);
     std::cout << ptrA1.use_count() << std::endl;
    std::shared_ptr<int> ptrB1 = std::make_shared<int>(20);
     std::cout << ptrB1.use_count() << std::endl;
    ptrA2 = ptrB1;
     std::cout << ptrA1.use_count() << std::endl;
     std::cout << ptrB1.use_count() << std::endl;
    return 0;
}

输出:1 2 1 1 2

 

智能指针陷阱:

https://blog.csdn.net/y1196645376/article/details/53023848

 

附:explicit用法

#include <iostream>
using namespace  std;

class A {
  public:
     explicit A(int a)
    {
        cout<<"创建类成功了!"<<endl;
    
    }
};
int main()
{
    A a=10;
    return 0;
}

上面的代码编译不成功,原因是当显式地定义了一个带一个参数的构造函数( 带explicit),必须要显示地调用构造函数,

A a(10);

如果不加 explicit的话,可以这样用

A a=10;

实际的转换过程如下:
相当于直接调用A(10)