c++ 智能指针

智能指针

简介

c++中动态内存的管理是通过new和delete来完成的,只要保证new和delete的配对使用,是没有问题的。但是有时候我们会忘记释放内存,甚至有时候我们根本就不知道什么时候释放内存。特别时在多个线程间共享数据时,更难判断内存该何使释放。这种情况下就机器容易产生引用非法内存的指针。

为了更容易(同时也更安全的管)的使用动态内存,新的标准库(C++11)提供了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似于常规指针。重要的区别是它负责自动释放所指向的对象。新标准提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则独占所指向的对象。标准库还定义了一个weak_ptr的伴随类,他是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中

指针类别

指针类别
shared_ptr
unique_ptr
weak_ptr
auto_ptr

shared_ptr

  • 初始化 shared_ptr
#include<iostream>
#include <memory>
using namespace std;

int main(){
  shared_ptr<string> p1; // 空指针
  if(!p1) cout << "p1 is nullptr" << endl;
  
  shared_ptr<string> p2(new string); // 初始化
  if (p2 && p2->empty()){
    *p2 = "hello world";
    cout << *p2 << endl;
  
  
  shared_ptr<string> pa = "hello world" // error 不允许以暴露的指针进行赋值操作
  
  // 一般的初始化方式
  shared_ptr<string> point(new string("hello world"));
  
  // 推荐的安全的初始化方式
  shared_ptr<string> point2 = make_shared<string>("hello world");
}
  • get() 函数

智能指针定义了一个名为get的函数,它返回一个内置指针,指向智能指针的管理的对象。此函数设置的初衷是当我们向不能使用智能指针的代码传递一个内置指针。使用get返回指针的代码不能delete此指针。
get用来将指针的访问权限传递给代码,只有在确定代码不会delete指针的情况下,才能使用get。特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值!

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

void useSharedPtr(int *p){
  cout << *p << endl;
}

void deletePoint(int *p) {
  delete p;
}

int main(){
  shared_ptr<int> p1 = make_shared<int>(32);
  useSharedPtr(p1.get());
  deletePoint(p1.get()); // error
}

  • make_shared 函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数,此函数在动态内存中分配一个对象并初始化它,返回此对象的shared_ptr。与只能指针一样,make_shared也定义在头文件memory中。

#include <iostream>
using namespace std;

int main()
{
  shared_ptr<int> p3 = make_shared<int>(42);
  cout<<*p3<<endl;

  shared_ptr<string> pstr = make_shared<string>("99999");
  cout<<*pstr<<endl;

  shared_ptr<int> pint = make_shared<int>(); //!默认初始化为 0
  cout<<*pint<<endl;

  auto pau = make_shared<string>("auto");    //!更简单,更常用的方式。
  cout<<*pau<<endl;
}

  • make_shared 的拷贝与赋值

当进行拷贝或者赋值操作时,每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象

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

int main(){
  auto p = make_shared<int>(42); // p指向的对象只有p一个引用者。
  cout << p.use_count() << endl; // 1
  auto q(p);  // p和q指向相同的对象,此对象有两个引用者。
  cout << q.use_count() << endl; // 2
  return 0;
}

  • shared_ptr 作为返回值
#include <iostream>
#include <memory>
using namespace std;

shared_ptr<string> factory(const char* p) {
  return make_shared<string>(p);
}

void useFactory(){
  shared_ptr<string> p = factory("Hello world");
  cout << *p << endl;
}### unique_ptr
### weak_ptr

shared_ptr<string> returnSharedPtr(){
  shared_ptr<string> p = factory("hello world");
  cout << *p << endl;
  retrun p;
}

int main() {
  useFactory();
  auto p = returnSharedPtr();
  cout << p.use_count() << endl;
}
  • 引用计数

可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr去初始化另一个shared_ptr;当我们给shared_ptr赋予一个新的值或者是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,他就会自动释放自己所管理的对象。

#include <iostream>
#include <memory>

using namespace std;

int main(){
  auto p = make_shared<int>(42);
  cout << p.use_count() << endl; // 1
  auto q = make_shared<int>(46);
  cout << q.use_count() << endl; // 1
  
  p = q;
  cout << *p << endl; // 46
  cout << *q << endl; // 46
  cout << p.use_count() << endl; // 2;
  cout << q.use_count() << endl; // 0
}
  • shared_ptr 其他操作

shared_ptr 还定义了一些其他的操作,参考前面的 shared_ptr 操作表格,例如,我们可以用 reset 将一个 新的指针赋予一个 shared_ptr:

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

int main() {
  shared_ptr<string> p1(new stirng("hello world"));
  p1.reset(new string("world hello")); // 与赋值类似,reset会更新(-1)引用计数,如果需要的话,会释放p1指向的对象。
  cout << *p1 << endl; // world hello
}
  • shared_ptr 与容器

对于一块内存,shared_ptr 类保证只要有任何shared_ptr对象引用它,他就不会被释放掉。由于这个特性,保证shared_ptr在不用之后不再保留就非常重要了,通常这个过程能够自动执行而不需要人工干预,有一种例外就是我们将shared_ptr放在了容器中。所以永远不要忘记erease不用的shared_ptr。

#include <iostream>
#include <memory>
#include <list>

using namespace std;

int main() {
  list<shared_ptr<string>> ptr_list;
  ptr_list.push_back(make_shared<string>("1111"));
  ptr_list.push_back(make_shared<string>("2222"));
  ptr_list.push_back(make_shared<string>("3333"));
  ptr_list.push_back(make_shared<string>("4444"));
  
  for (auto p : ptr_list){
    if (*p == "3333") {
      /* do some thing */
    }
    cout << *p << endl;
  }
  /*包含"3333"的数据我们已经使用完了!*/
  list<shared_ptr<string>>::iterator itr = ptr_list.begin();
  for(;itr!=ptr_list.end();++itr) {
    if(**itr == "3333"){
      cout<<**itr<<endl;
      ptr_list.erase(itr);
    }
  }
  
  cout<<"-------------after remove------------"<<endl;
  for(auto p:ptr_list) {
    cout<<*p<<endl;
  }
  
 while(1){
  /*do somthing other works!*/
  /*遍历 ptr_list*/    //!这样不仅节约了大量内存,也为容器的使用增加了效率  
 }
}
  • shared_ptr 状态共享
#include <iostream> 
#include <memory>
#include <list>
using namespace std;

// v1 与 v2 属于两个不同的对象, 一个改变不会影响另外一个
void copyCase(){
  list<string> v1({"1", "2", 3});
  list<string> v2 = v1; // v1 与 v2 占用同一段内存
  v1.push_back("11"); // v1 != v2
  
  for (auto &p : v1) {
    cout << p << endl;
  }
  
  for (auto *p : v2) {
    cout << p << endl;
  }
} 

// v1和v2属于一个对象的两个引用,有引用计数为证,其内容的改变是统一的。
void SharedCase(){
  shared_ptr<list<string>> v1 = make_shared<list<string>>(1, "aa");
  shared_ptr<list<string>> v2 = v1;
  (*v1).push_back("bb");
  for(auto &p : *v1) {
    cout << p << endl;
  }
  
  for (auto &p : *v2) {
    cout << p << endl;
  }
}

int main() {
  copyCase();
  SharedCase();
}

  • 总结
  1. 不使用相同的内置指针值初始化(或reset)多个智能指针。
  2. 不delete get函数返回的指针。
  3. 如果你使用了get返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了。
  4. 如果你使用智能指针管理的资源不是new分配的内存,记得传递给他一个删除器。

unique_ptr

一个unique_ptr"拥有“他所指向的对象。与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定的对象。当unique_ptr被销毁时,它所指向的对象也被销毁。uniptr_ptr表达的是一种独占的思想。

例子

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

int main() {
  unique_ptr<double> p1;
  unique_ptr<int> p1(new int(56));
  unique_ptr<string> pstr(new string("hello world"));
  unique_ptr<stirng> pstr2(pstr); // error 不支持拷贝 唯一性
  
  unique_ptr<stirng> pstr3 = pstr; // error 不支持赋值 唯一性
  return 0;
}
  • 常见操作
unique_ptr<T> p1;    // 一个指向T对象的空 unique_ptr 指针类别
unique_ptr<T, D> p2;
unique_ptr<T, D> p3(d);
p3 = nullptr;
p3.release();
p3.reset();
p3.reset(p1);
p3.reset(q);
p3.reset(nullptr);
  • 所有权转移

虽然我们不能拷贝赋值 unique_ptr,但是可以通过调用release或者set将指针的所有权从一个(非const) unique_pt r转移给一个 unique:

#include <iostream>
#include <memory>

using namespace std;

class Test{
public:
  Test(const string& name):_name(name){
    cout << "Test: " << _name << endl;
  }
  
  Test(const Test& another){
    _name = another.name;
    cout << another._name << " copy struct " << _name << endl;
  }
  
  Test& operator =(const Test& another) {
    if (&another == this) return *this;
    this->_name = another._name;
    cut << another._name << "copy Assin to " << _name;
  }
  
  ~Test(){
    cout << "~Test: " << _name << endl;
  }
  
  string _name;
};

int main(){
  unique_ptr<Test> p1(new Test("case 1");
  unique_ptr<Test> p2(p1.release()); // 将所有权从p1转到p2,p1现在指向nullptr
  
  unique_ptr<Test> p3(new Test("case 2"));
  p2.reset(p3.release()); // p2释放了原来指向的内存,接受了p3指向的内存
}
  • unique_ptr 作为参数的传递与返回

不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或者赋值一个将要被销毁的unique_ptr。其本质就是调用了移动拷贝和移动赋值;最常见的例子是从函数返回一个unique_ptr:

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

class Test{
public:
  Test(const string& name):_name(name){
    cout << "Test: " << _name << endl;
  }
  
  Test(const Test& another){
    _name = another.name;
    cout << another._name << " copy struct " << _name << endl;
  }
  
  Test& operator =(const Test& another) {
    if (&another == this) return *this;
    this->_name = another._name;
    cut << another._name << "copy Assin to " << _name;
  }
  
  ~Test(){
    cout << "~Test: " << _name << endl;
  }
  
  string _name;
};

// 返回一个即将被销毁的 unique_ptr
unique_ptr<Test> retDying(stirng param) {
  reurn unique_ptr<Test>(new Test(param));
}

// 返回一个局部对象
unique_ptr<Test> retTemp(string param) {
  unique_ptr<Test> pTemp(new Test(param));
  reurn pTemp;
}

int main() {
  unique_ptr<Test> ret1 = retDying("dying");
  cout << (*ret1)._name << endl;
  
  unique_ptr<Test> ret2 = retTemp("temp");
  cou << (*ret2)._name << endl;
}

  • 数组
unique_ptr<T[]> u; // u指向一个动态分配的数组,数组类型为T
unique_ptr<T[]> u(p); // u指向内置指针p所指向的动态分配的数组,p必须能转换为类型 T*

weak_ptr

auto_ptr

参考文献

【1】https://www.cnblogs.com/wangkeqin/p/9351191.html
【2】https://www.cnblogs.com/wangkeqin/p/9383658.html

发布了26 篇原创文章 · 获赞 19 · 访问量 8万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览