5.2 智能指针(smart pointer)

点点圈 提交于 2020-03-05 08:26:34

于头文件<memory>

1. shared_ptr实现共享拥有(shared ownership),标准库还提供了weak_ptr  bad_weak_ptr和enable_shared_from_this等辅助类

2. unique_ptr实现独占式拥有(exclusive ownership/strict ownership),

3. 特点

    shared_ptr和weak_ptr内部需额外的辅助对象(引用计数器等),因此无法完成一些优化操作

    unique_ptr不需要这些额外的开销(unique_ptr特殊的构造和析构、copy语义的消除),unique_ptr消耗的内存和native pointer相同,还可使用function object(包括lambda)作为deleter达成最佳优化,甚至零开销。

    smart pointer并不保证线程安全,虽然它有适用某些保证。

目录

share_ptr

weak_ptr

unique_ptr


share_ptr

  • shared_ptr基本使用
//1. 直接初始化
shared_ptr<string> test(new string("nico"));
shared_ptr<string> test{new string("nico")};

//2. 使用便捷函数make_shared(),快、安全、使用一次分配
shared_ptr<string> test = make_shared<string>("nico");

//3. 先声明,然后赋值(需使用reset(),不能使用赋值运算符)
shared_ptr<string> test;
test = new string("nico");     //Error
test.reset(new string("nico"));//OK

//错误使用
int* p = new int;
std::shared_ptr<int> sp1(p);
std::shared_ptr<int> sp2(p); //Error:two shared pointers manage allocated int
//正确使用
std::shared_ptr<int> sp1(new int);
std::shared_ptr<int> sp2(sp1);

线程安全(Thread-Safe)的Shared Pointer接口

shared pointer并非线程安全,通常为避免data race须使用如mutex或lock等。但当一个线程修改对象时,其他线程读取其使用次数并不会造成data race,虽然可能读到的值不是最新的。

对付Array

shared_ptr提供的default deleter调用的是delete,不是deleter[]。因此只有当shared pointer拥有“由new建立的单一对象”,default deleter才适用

std::shared_ptr<int> p(new int[10]); //Error,but compiles
std::shared_ptr<int> p(new int[10],  //new[]一个array of object须定义deleter
                       [](int* p) {
                         delete[] p;
                       });
//使用为unique_ptr提供的辅助函数作为deleter(内部调用了delete[])
std::shared_ptr<int> p(new int[10],
                       std::default_delete<int[]>());

处理共享内存

#include <memory>     //for shared_ptr
#include <sys/mman.h> //for shared memory
#include <fcntl.h>
#include <unistd.h>
#include <cstring>    //for strerror()
#include <cerrno>     //for errno
#include <string>
#include <iostream>
/**
 *@brief 卸除(detach) shared memory 
 */
class SharedMemoryDetacher {
 public:
  void operator() (int* p) {
    std::cout<<"unlink /tmp1234"<<std::endl;
    if (shm_unlink("/tmp1234") != 0) {
      std::cerr<<"OOPS: shm_unlink() failed"<<std::endl;
    }
  }
};
/**
 * @brief 取得并附着(attach) shared memory
 */
std::shared_ptr<int> getSharedIntMemory (int num) {
  void* mem;
  int shmfd = shm_open("/tmp1234", O_CREAT|O_RDWR, S_IRWXU|S_IRWXG);
  if (shmfd < 0) {
    throw std::string(strerror(errno));
  }

  if (ftruncate(shmfd, num*sizeof(int)) == -1) {
    throw std::string(strerror(errno));
  }
  mem = mmap(nullptr, num*sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0);
  if (mem == MAP_FAILED) {
    throw std::string(strerror(errno));
  }

  //SharedMemoryDetacher将在最后一次被使用时调用
//   return std::shared_ptr<int>(static_cast<int*>(mem), SharedMemoryDetacher());
  //使用lambda
  return std::shared_ptr<int>(static_cast<int*>(mem),
                              [](int* p){
                                std::cout<<"unlink /tmp1234 "<<std::endl;
                                if (shm_unlink("/tmp1234") != 0) {
                                  std::cerr<<"OOPS: shm_unlink() failed"<<std::endl;
                                }
                              });
}
int main() {
  //get and attach shared memory for 100 ints
  std::shared_ptr<int> smp(getSharedIntMemory(100));
  
  //init the shared memory
  for (int i = 0; i < 100; ++i) {
    smp.get()[i] = i*42;//通过get()获得被shared_ptr包裹(wrapped)的内部指针
  }

  //deal with shared memory somewhere else

  std::cout<<"<return>"<<std::endl;
  std::cin.get();

  //release shared memory here:
  smp.reset();
  return 0;
}

weak_ptr

  • 下面2种情况shared_ptr将会遇到问题
  1. cyclic reference(环式指向)。如果两个对象使用shared_ptr互相只想对方,每个对象的use_count()都为1,反而都无法释放。
  2. 在你“明确的只是想共享但不愿拥有”某对象的情况下。你要的语义是:reference的寿命比其所指对象的寿命更长。
  • class wake_ptr允许你“共享但不拥有”对象(use_count()返回的是对象被shared_ptr拥有的次数),即在最后一个拥有该对象的shared_ptr失去拥有权后,weak_ptr会自动成空(empty),class weak_ptr只提供“接受一个shared_ptr”的构造函数。
  • 不能使用操作符*和->访问weak_ptr指向的对象,而必须另外建立一个shared_ptr,(weak_ptr只能用来创建、复制、赋值weak pointer,以及转换为一个shared pointer或检查自己是否指向某对象)基于下面2个理由
  1. 在wak pointer之外建立一个shared pointer检查是否存在一个相应的对象。如果不存在,将会抛出异常或。。。
  2. 当指向的对象正被处理时,shared pointer无法被释放

        

#include <iostream>
#include <string>
#include <vector>
#include <memory>

class Person {
 public:
  std::string name;
  std::shared_ptr<Person> mother;
  std::shared_ptr<Person> father;
//   std::vector<std::shared_ptr<Person>> kids; //出问题,每个Person的析构不会被调用
  std::vector<std::weak_ptr<Person>> kids;//parent到kids为weak_ptr, kids到parent为shared_ptr

  Person(const std::string& n,
         std::shared_ptr<Person> m = nullptr,
         std::shared_ptr<Person> f = nullptr)
      : name(n), mother(m), father(f) {
  }
  ~Person() {
    std::cout<<"delete "<<name<<std::endl;
  }
};
std::shared_ptr<Person> initFamily(const std::string& name) {
  std::shared_ptr<Person> mom(new Person(name+"'s mom"));
  std::shared_ptr<Person> dad(new Person(name+"'s dad"));
  std::shared_ptr<Person> kid(new Person(name, mom, dad));

  mom->kids.push_back(kid);
  dad->kids.push_back(kid); 
  return kid;
}

int main() {
  std::shared_ptr<Person> p = initFamily("nico");
//   p->mother->kids[0]->name;       //Error,weak_ptr不能使用操作符*和->
  p->mother->kids[0].lock()->name;
  std::cout<<"nico's family exists"<<std::endl;
  std::cout<<"- nico is shared "<<p.use_count()<<" times"<<std::endl;
  std::cout<<"- name of 1st kid of nico's mom:"<<p->mother->kids[0]->name<<std::endl;

  p = initFamily("jim");
  std::cout<<"jim's family exists"<<std::endl;
  return 0;
}

unique_ptr

C++11起开始提供,是一种在异常发生时可帮助避免资源泄露的smart pointer, 确保一个对象和其相应资源同一时间只被一个pointer拥有。

使用普通指针

因为显示获得资源,不与任何对象捆绑,所以必须被明确释放。但程序若在delete前发生异常将导致内存泄漏(资源泄漏),而使用unique_ptr则不论程序正常或异常结束都能够实现自动销毁。

/**
*@brief 以new和delete创建和销毁对象
*/
void f() {
  ClassA* ptr = new ClassA; //create an object explicitly
  ...          //perform some operations
  delete ptr;  //clean up(destory the object explicitly)
}

//为避免资源泄露
void f() {
  ClassA* ptr = new ClassA; //create an object explicitly
  try {
    ... //perform some operations
  }
  catch(...) {
    delete ptr;  //clean up
    throw;       //throw the exception
  }
  delete ptr;    //clean up normal
}

使用unique_ptr

使用*提领(dereference)指向对象,操作符->用来访问成员

//初始化
std::unique_ptr<int> ptr = new int; //Error
std::unique_ptr<int> ptr1(new int);  //OK
std::unique_ptr<int> ptr2(ptr1);     //Error, unique_ptr不支持copy构造和assignment
std::unique_ptr<int> ptr3(std::move(ptr1));//Ok,ptr1将拥有权移交给ptr3
std::unique_ptr<int> ptr4;
ptr4 = ptr1;           //Error,no possible
ptr4 = std::move(ptr1);//ok,不能共享,但可以转移拥有权

//没有任何2个unique_ptr以同一个pointer作为初值
std::string* sp = new std::string("hello");
std:unique_ptr<std::string> up1(sp);
std:unique_ptr<std::string> up2(sp); //Error:up1 and up2 own same data

std::unique_ptr<int> ptr; //ptr此时不拥有对象,为空,ptr == nullptr和ptr.reset()同样置空
ptr.reset(new int);

//调用release()
std::unique_ptr<std::string> up(new std::string("nico"));
...
std::string* sp = up.release(); //up lose owner ship
if (up) { ... }                  //检查unique_ptr是否拥有对象
if (up != nullptr) { ... }       //检查unique_ptr是否拥有对象
if (up.get() != nullptr) { ... } //检查unique_ptr是否拥有对象

 

from book《C++标准库》第5章

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!