c++智能指针

智能指针的由来

        早期的c++中是没有任何内存回收机制的,只能通过程序员手动的在适当的地方delete语句回收,对于早期的语言来说本身也是合理的。但是难就难在适当的地方太难把控,导致c++程序后期出现各种内存回收问题。
        内存回收的目的就是为了程序中不使用哪一块堆内存时,系统可以及时的回收,如果不使用智能指针,单单使用c++11以前的语法也是可以实现的,依靠的就是栈对象在离开作用域时会自动回收空间,如果是类或者结构体的实例还会调用析构函数,那我们是不是可以将堆内存的对象封装在栈对象中,运用栈内存自动回收空间的性质回收堆内存。

template <typename T>
class SmartPtr {
 public:
  SmartPtr() {}
  SmartPtr(T *ptr) : _ptr(ptr) {}
  SmartPtr(const SmartPtr &smart_ptr) { _ptr = smart_ptr._ptr; }
  SmartPtr &operator=(const SmartPtr &form) { _ptr = form._ptr; }
  T *operator->() { return _ptr; }

  void print() { cout << *_ptr << endl; }
  ~SmartPtr() { delete _ptr; }

 private:
  T *_ptr;
};

        如上代码所示实现了一个非常基本的智能指针的例子,虽然很简陋,但是可以在局部变量作用域结束时释放空间,这个是没有什么卵用,但就是在这个简单的思想上衍生中各种智能指针。

智能指针的类型

类代码

class shape {
 public:
  shape() = default;
  shape(const string &name) : _name(name) {}
  virtual void print() { cout << _name << endl; }
  virtual double perimeter() { return 0; };
  virtual double area() { return 0; };
  ~shape() {}

 private:
  string _name;
};

class cricle : public shape {
 public:
  cricle() = default;
  cricle(const string &name, const float &radius) : shape(name), _r(radius) {}
  double perimeter() override { return _r * 2 * PI; }
  double area() override { return _r * PI * PI; }
  ~cricle() {}

 private:
  float _r;
};

auto_ptr

        auto_ptr很多问题,目前在c++17中已经被废弃了,即使是c++17之前的版本也不推荐使用,主要的问题是auto_ptr会出让所有权,导致出让所有权之前的智能指针存在但是使用时会导致程序段错误。

  auto_ptr<cricle> sa(new cricle("圆形", 10));

  auto_ptr<cricle> sa1 = sa; //成功
  sa1->print();  //打印 圆形
  cout << sa1->area() << endl; //打印 98.596 
  sa->print();  //段错误
  cout << sa->area() << endl;

执行结果

圆形
98.596
[1]    52190 segmentation fault 

        我们可以看到,拷贝构造成立的情况下,sa指向指针的所有权会被出让给sa1,导致原来的智能指针悬空,导致段错误。
        auto_ptr可以看做不允许多引用的指针,同一时刻只能有一个auto_ptr封装并使用,但是赋值语义和拷贝构造函数应该是不可以被调用,但是通过我们的结果可以看到,并不是这样。这依然违背了c++的编程思想,并且极易出错。
auto_ptr可以通过move进行所有权转移,move之后语义明确。

unique_ptr

由于auto_ptr的种种问题,我们应当使用unique_ptr来替代auto_ptr,它们两个唯一的区别是unique_ptr不支持拷贝构造和赋值的语义,只支持move,目的依然是同一时刻只能有一个智能指针指向一个堆对象地址。但是比auto_ptr语义更加明确。

  unique_ptr<cricle> sa(new cricle("圆形", 10));
  sa->print();
  cout << sa->area() << endl;
  auto sa1 = sa;

执行结果

smart_ptr.cpp:51:8: error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr<cricle, std::__1::default_delete<cricle> >'
  auto sa1 = sa;

使用move语义

  unique_ptr<cricle> sa(new cricle("圆形", 10));
  sa->print();
  cout << sa->area() << endl;
  auto sa1 = move(sa);
  sa1->print();
  cout << sa1->area() << endl;

scoped_ptr

scoped_ptr并非标准库的中指针类型,而是boost库中的。放在这里讲述是为了和以上两个指针做对比分析。scoped_ptr和以上两个智能指针最大的不同是不允许任何形式的出让所有权,上面两种智能指针可以通过复值或者move的方式出让所有权,导致他们的声明周期是根据当前有没有智能指针引用来决定的,而scoped_ptr不允许任何形式的所有权出让,这就意味着智能指针只能在当前作用域内起作用,离开作用域后就会自动回收分配的堆空间。

  boost::scoped_ptr<cricle> sa(new cricle("圆形", 10));
  sa->print();
  cout << sa->area() << endl;
  auto sa1 = move(sa);/auto sa1 = sa;

执行结果

smart_ptr.cpp:52:14: error: calling a private constructor of class 'boost::scoped_ptr<cricle>'

我们可以看到无论是拷贝构造还是move都会提示编译错误,说明scoped_ptr只能在一个作用域中起作用。
这里我们看到这里提示的是构造函数是静态函数,不难猜出boost是如何实现构造函数、复制运算符、移动是如何被禁止调用的。如果不显示声明这些函数,编译器会自动声明出默认的函数,所以想要禁止构造函数,只需要将这些函数显示声明成private,但是不用定义。在编译过程中就会显示调用失败了。如下

class A {
  private:
    A(const A &a); //拷贝构造函数
    A(const A &&a); //移动构造函数
    A& operator=(const A &A); //复制函数
};

shared_ptr

上面三个指针有一个共同的特点,就是同一时刻只能有一个指针有效,但是在开发中需要大量的多指针指向同一空间的情况,以上三种指针都不能满足这样的情况,所以说在开发过程中用到的最多的智能指针其实是shared_ptr,它与以上三种指针最大的不同是可以使用多个指针共享。但是这里要解决一个以上三种智能指针不需要关注的问题,就是一个共享指针调用析构函数时,如何不影响其他的引用者。
stl采用了引用计数的方法,原理很简单,就是每多出一个新的共享指针就将引用计数值加1,而析构一个共享指针,就将这个值减1,如果析构时引用计数为1,则释放内存空间。

  shared_ptr<cricle> sa(new cricle("圆形", 10));
  cricle *c = sa.get();
  auto sa1 = sa;
  
  cout << c->area() << endl;
  sa1.reset();
  cout << c->area() << endl;
  sa.reset();
  cout << c->area() << endl;

执行结果

98.596
98.596
[1]    53532 segmentation fault

从以上现象可以看出,共享指针释放后,并不会影响其他的引用和指向内存中内容。只有所有引用都不再指向内存,这块内存才会释放,再次调用该空间,就会引起段错误。

weak_ptr

我认为weak_ptr并不算常规意义上的智能指针,它应是shared_str的附属指针,功能是检测一个shared_str指针的引用数量,并且不占用引用计数,就是即使将shared_str赋值给weak_ptr,也不会增加引用计数值,但是不同的是weak_ptr不能访问指向内存的任何方法或者获得成员值。

  shared_ptr<cricle> sa(new cricle("圆形", 10));
  cout << sa.use_count() << endl;

  shared_ptr<cricle> sa1 = sa;
  cout << sa.use_count() << endl;
  
  weak_ptr<cricle> wp = sa1;
  cout << sa.use_count() << endl;

执行结果

1
2
2

当使用weak_ptr调用方法时报如下错误

smart_ptr.cpp:57:6: error: no member named 'print' in 'std::__1::weak_ptr<cricle>'
  wp.print();

weak_ptr也可以调用lock函数获得一个shared_ptr,可以临时使用,也可以存储到另一个变量里,这样就可以像shared_ptr使用了,只是引用计数会加1。

总结:分为两类总结
只允许单个智能指针引用
1.auto_ptr可以出让所有权给其他auto_ptr,但是同时支持拷贝构造和赋值运算符,语义不明确。不推荐使用
2.unique_ptr可以出让所有权给其他auto_ptr,不支持拷贝构造和赋值运算符,但是支持move语义,当只需要一个引用时使用。
3.scoped_str不能出让所有权,不支持拷贝、赋值运算符以及move语义,当只需要在局部使用并为了保证不被其他智能指针引用时使用。
允许多个智能指针引用
4.shared_str共享的智能指针,采用引用计数的方式,只有最后一个引用的变量析构时才会回收堆空间。适合在需要多个地方同时使用时使用。
5.weak_str可以看做shared_str的附属指针,可以用来检测shared_str是否有引用计数。不可以访问指针指向的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值