您现在的位置是: 网站首页 > 程序设计  > C++ 

C++智能指针

2020年6月22日 08:00 936人围观

简介C++11提供了全新的指针指针shared_ptr,接下来我们具体看一下它的用法

shared_ptr 智能指针

std::shared_ptr采用引用计数,每一个shared_ptr的拷贝都指向相同的内容,当最后一个shared_ptr析构的时候,内存被释放

首先定义一个Person类

class Person 
{ 
public: 
    static int index; 
    Person() 
    { 
        idx = Person::index++; 
        printf("create #%d person object\n", idx); 
    } 

    ~Person() 
    { 
        printf("destory #%d person object\n", idx); 
    } 
    void say() 
    { 
        printf("I'm #%d person, my name is %s\n", idx, name.c_str()); 
    } 

    void set_name(const std::string &s) 
    { 
        name = s; 
    } 

private: 
    int idx; 
    string name; 
}; 

int Person::index = 0; 

创建

创建share_ptr有四种方法,分别为new、=、reset、make_shared

  • new操作

new操作将一个原始指正转换成一个智能指针,转换后原始指针和指针指针指向同一块地址

  int main() 
  { 
      Person *person = new Person(); 
      std::shared_ptr<Person> ptr(person); 

      person->say(); 
      ptr->say(); 

      person->set_name("zhangsan"); 
      person->say(); 
      ptr->say(); 

      ptr->set_name("lisi"); 
      ptr->say(); 
      person->say(); 
      cout << (person == ptr.get()) << endl; 
  } 

上述代码输出:

  create #0 person object 
  I'm #0 person, my name is  
  I'm #0 person, my name is  
  I'm #0 person, my name is zhangsan 
  I'm #0 person, my name is zhangsan 
  I'm #0 person, my name is lisi 
  I'm #0 person, my name is lisi 
  1 
  destory #0 person object 

对share_ptr和原始指针的操作都会互相影响,可以认为操作的是同一块地址,person == ptr.get()输出了1。

  1. ptr.get()返回的是const指针,也就是说只能访问,不能修改。

  2. 切记不要用一个new出来的原始指针去创建多个share_ptr指针,这样一来同一块地址被不同的智能指针引用,但是这些智能指针没有公用一个计数器,这就导致该内存会被释放多次。

  3. std::shared_ptr<Person> ptr(person); 这种方式将转换时,person的所指对象必须是堆上申请的,如果用栈上的变量,就会出问题。

     Person person; 
     std::shared_ptr<Person> ptr(&person); 

这段代码在退出person作用域时会出现问题,因为局部对象会被自动释放,而当share_ptr检查到引用计数为0时也会释放,相当于一个对象被释放了两次。

  • =操作

=将一个share_ptr拷贝到另一个,两个公用一个地址,引用计数+1

  int main() 
  { 
      Person *person = new Person; 
      std::shared_ptr<Person> ptr1(person); 
      cout << ptr1.use_count() << endl; 

      std::shared_ptr<Person> ptr2 = ptr1; 
      cout << ptr1.use_count() << " " << ptr2.use_count() << endl; 

      cout << (ptr1.get() == ptr2.get()) << endl; 
  } 

上面代码输出

  create #0 person object 
  1 
  2 2 
  1 
  destory #0 person object 

由此可以,ptr1和ptr2引用了同一个地址

  • reset操作

如果reset的指针已经引用了其他对象,则在执行reset的时候会先释放原有对象,即原有对应引用计数-1

      Person *person = new Person; 
      std::shared_ptr<Person> ptr1(person); 
      cout << ptr1.use_count() << endl; 

      std::shared_ptr<Person> ptr2 = ptr1; 
      cout << ptr1.use_count() << " " << ptr2.use_count() << endl; 

      cout << (ptr1.get() == ptr2.get()) << endl; 

      ptr2.reset(new Person()); 
      cout << ptr1.use_count() << " " << ptr2.use_count() << endl; 

      cout << (ptr1.get() == ptr2.get()) << endl; 

上述代码输出

  create #0 person object 
  1 
  2 2 
  1 
  create #1 person object 
  1 1 
  0 
  destory #1 person object 
  destory #0 person object 

执行reset后,ptr1和ptr2指向了不同的地址

  • make_shared操作
  int main() 
  { 
      std::shared_ptr<Person> ptr = std::make_shared<Person>(); 
      ptr->say(); 
  } 

代码输出

  create #0 person object 
  I'm #0 person, my name is  
  destory #0 person object 

创建指针的是推荐使用make_shared方法。

删除

删除可以分为reset释放删除和赋值null删除,这两者原理其实是一样的,都是释放控制权,引用计数-1,如果计数为0,则释放内存

  • reset释放指针的控制权
  int main() 
  { 
      std::shared_ptr<Person> ptr = std::make_shared<Person>(); 
      cout << ptr.use_count() << endl; 

      ptr.reset(); 
      cout << ptr.use_count() << endl; 
  } 

上面代码输出

  create #0 person object 
  1 
  destory #0 person object 
  0 
  • 赋值null删除

将上段代码中的ptr.reset替换为ptr = nullptr 即可,输出结果完全一样

自定义删除器

这里分为两种情况,一种时析构函数中不能完成指定的逻辑,一种是创建指针的时候传入了数组。这时需要传入我们自定义的删除器,当在析构对象的时候就不再调用析构函数了,而是调传入的删除器。

void deleter(Person *p) 
{ 
    cout << "my destory" << endl; 
    delete p; 
} 

int main() 
{ 
    Person *p = new Person; 
    std::shared_ptr<Person> ptr(p, deleter); 
} 

如果在创建share_ptr的时候不传入删除器,则在释放对象的时候会调用定义的析构函数。但如果在创建的时候传入一个删除器,则在析构的时候会调用删除器,而不直接调析构函数了。

假如初始化的时候传入了个数组,如下

int main() 
{ 
    Person *p = new Person[4]; 
    std::shared_ptr<Person> ptr(p); 
} 

代码输出

create #0 person object 
create #1 person object 
create #2 person object 
create #3 person object 
destory #0 person object 

可见,创建了4个人对象,但只析构了一个。在使用原始指针创建的数组的时候,如果删除的是数组,那么我们一般会写上[]符号,比如delete []a。所以我们可以在自定义的删除器中增加delete[] 操作。

void deleter(Person *p) 
{ 
    cout << "my destory" << endl; 
    delete []p; 
} 

int main() 
{ 
    Person *p = new Person[4]; 
    std::shared_ptr<Person> ptr(p, deleter); 
} 

最终输出

create #0 person object 
create #1 person object 
create #2 person object 
create #3 person object 
my destory 
destory #3 person object 
destory #2 person object 
destory #1 person object 
destory #0 person object