美文网首页
C++11 智能指针

C++11 智能指针

作者: Leo_JiangZhiHao | 来源:发表于2023-08-06 10:56 被阅读0次

为什么要使用智能指针?

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。
使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。

使用智能指针

class Test {
public:
  Test() { cout << "Test的构造函数..." << endl; }
  ~Test() { cout << "Test的析构函数..." << endl; }
  int getDebug() { return this->debug; }
private:
  int debug = 20;
};

定义智能指针:

auto_ptr<Test> test(new Test);

智能指针可以像普通指针那样使用:

cout << "test->debug:" << test->getDebug() << endl;
cout << "(*test).debug:" << (*test).getDebug() << endl;

为什么智能指针可以像普通指针那样使用?

因为其里面重载了 * 和 -> 运算符, * 返回普通对象,而 -> 返回指针对象。

智能指针的三个常用函数:

1、get() 获取智能指针托管的指针地址

auto_ptr<Test> test(new Test);
Test *tmp = test.get(); // 获取指针返回

2、release() 取消智能指针对动态内存的托管

auto_ptr<Test> test(new Test);
Test *tmp2 = test.release(); // 取消智能指针对动态内存的托管
delete tmp2; // 之前分配的内存需要自己手动释放

3、reset() 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉

auto_ptr<Test> test(new Test);
test.reset(); // 释放掉智能指针托管的指针内存,并将其置NULL
test.reset(new Test()); // 释放掉智能指针托管的指针内存,并将参数指针取代之

reset函数会将参数的指针(不指定则为NULL),与托管的指针比较,如果地址不一致,那么就会析构掉原来托管的指针,然后使用参数的指针替代之。然后智能指针就会托管参数的那个指针了。

auto_ptr(C++98的方案,C++11已经抛弃)

C++11后不建议使用auto_ptr。

auto_ptr<string> p1 (new string ("auto”));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错

此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1指向的内容将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!

unique_ptr(替换auto_ptr)

unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。
它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。

还是上面那个例子:

unique_ptr<string> p3 (new string ("unique"));
unique_ptr<string> p4;
p4 = p3; //此时会报错

编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。

万一我就是需要把一个unique_ptr智能指针赋值给另外一个怎么办呢?
可以使用移动函数!如下:

unique_ptr<Obj> ptr1( new Obj() );
unique_ptr<Obj> ptr2( std::move(ptr1) );

这个效果和auto_ptr直接赋值是一样的,就是ptr1不再拥有Obj对象了,所以ptr1不能再用来操作内存中的Obj对象。

unique_ptr和auto_ptr在使用上的区别

1、无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值

unique_ptr<string> p1(new string("I'm Li Ming!"));
unique_ptr<string> p2(new string("I'm age 22."));
    
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;

p1 = p2; // 禁止左值赋值
unique_ptr<string> p3(p2); // 禁止左值赋值构造

unique_ptr<string> p3(std::move(p1));
p1 = std::move(p2); // 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样

2、在 STL 容器中使用unique_ptr,不允许直接赋值

vector<unique_ptr<string>> vec;
unique_ptr<string> p3(new string("I'm P3"));
unique_ptr<string> p4(new string("I'm P4"));

// 必须使用std::move修饰成右值,才可以进行插入容器中
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));

vec[0] = vec[1]; // 不允许直接赋值
vec[0] = std::move(vec[1]); // 需要使用move修饰,使得程序员知道后果
vector<auto_ptr<string>> vec;
auto_ptr<string> p3(new string("I'm P3"));
auto_ptr<string> p4(new string("I'm P4"));

// 必须使用std::move修饰成右值,才可以进行插入容器中
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));

vec[0] = vec[1];  // 风险来了
cout << "vec[1]:" << *vec[1] << endl; // 运行崩溃

3、支持对象数组的内存管理

// 会自动调用delete [] 函数去释放内存
unique_ptr<int[]> array(new int[5]); // 支持这样定义
auto_ptr<int[]> array(new int[5]); // 不能这样定义

shared_ptr

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。
从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。
除了可以通过new来构造,还可以通过传入auto_ptr、unique_ptr、weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,use_count(用指针实现,存在堆上)计数减一。当计数等于0时,资源会被释放。
shared_ptr是为了解决auto_ptr在对象所有权上的局限性(auto_ptr 是独占的),使用引用计数机制提供可以共享所有权的智能指针。

共享指针的使用

初始化

方式一:构造函数

shared_ptr<int> up1(new int(10)); // int(10) 的引用计数为1
shared_ptr<int> up2(up1); // 使用智能指针up1构造up2, 此时int(10) 引用计数为2

方式二:使用make_shared 初始化对象,分配内存效率更高(推荐使用)
make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。
用法:make_shared<类型>(构造类型对象需要的参数列表);

shared_ptr<int> up3 = make_shared<int>(2); // 多个参数以逗号','隔开,最多接受十个
shared_ptr<string> up4 = make_shared<string>("字符串");
shared_ptr<Person> up5 = make_shared<Person>(9);

引用计数

调用use_count函数可以获得当前托管指针的引用计数。

shared_ptr<Person> sp1;
shared_ptr<Person> sp2(new Person(2));
cout << "sp1    use_count() = " << sp1.use_count() << endl;
cout << "sp2    use_count() = " << sp2.use_count() << endl;

赋值

shared_ptr<int> up1(new int(10)); // int(10) 的引用计数为1
shared_ptr<int> up2(new int(11)); // int(11) 的引用计数为1
up1 = up2; // int(10) 的引用计数减1,计数归零内存释放,up2共享int(11)给up1, int(11)的引用计数为2

主动释放对象

shared_ptr<int> up1(new int(10));
up1 = nullptr; // int(10) 的引用计数减1,计数归零内存释放 
// 或
up1 = NULL; // 作用同上

交换

// p1 和 p2 是智能指针
std::swap(p1,p2); // 交换p1和p2管理的对象,原对象的引用计数不变
p1.swap(p2); // 交换p1和p2管理的对象,原对象的引用计数不变

shared_ptr什么时候改变引用计数?

1、构造函数中计数初始化为1;
2、拷贝构造函数中计数值加1;
3、赋值运算符中,左边的对象引用计数减1,右边的对象引用计数加1;
4、析构函数中引用计数减1;
5、在赋值运算符和析构函数中,如果减一后为0,则调用delete释放对象。

weak_ptr

weak_ptr是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理的对象。进行该对象的内存管理的是那个强引用的shared_ptr。weak_ptr只是提供了对管理对象的一个访问手段。
weak_ptr设计的目的是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它只可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。
它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

class A
{
public:
  shared_ptr<B> pb_;
  ~A()
  {
    cout<<"A delete\n";
  }
};
class B
{
public:
  shared_ptr<A> pa_;
  ~B()
  {
    cout<<"B delete\n";
  }
};
void fun()
{
  shared_ptr<B> pb(new B());
  shared_ptr<A> pa(new A());
  pb->pa_ = pa;
  pa->pb_ = pb;
  cout<<pb.use_count()<<endl;
  cout<<pa.use_count()<<endl;
}
int main()
{
  fun();
  return 0;
}

可以看到fun函数中pa,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用)。
如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_,改为weak_ptr pb_。这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。

智能指针有没有内存泄漏的情况?如何解决?

当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
引入weak_ptr弱指针,weak_ptr的构造和析构不会引起引用记数的增加或减少,用来解决shared_ptr相互引用时的死锁问题。

弱指针的使用

weak_ptr wpGirl_1; // 定义空的弱指针
weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针

弱指针也可以获得引用计数:

wpGirl_1.use_count();

弱指针不支持 * 和 -> 对指针的访问:weak_ptr 没有重载 * 和 ->

wpGirl_1->setBoyFriend(); // 编译报错
(*wpGirl_1)->setBoyFriend(); // 编译报错

在必要的使用lock()可以转换成共享指针:

shared_ptr<Girl> sp_girl;
sp_girl = wpGirl_1.lock();

// 使用完之后,再将共享指针置NULL即可
sp_girl = NULL;

智能指针的使用陷阱

不要把一个原生指针给多个智能指针管理

int *x = new int(10);
unique_ptr< int > up1(x);
unique_ptr< int > up2(x);
// 警告! 以上代码使up1、up2指向同一个内存,非常危险

或以下形式:

up1.reset(x);
up2.reset(x);

记得使用release()的返回值

在调用release()时是不会释放是真所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了。

禁止delete智能指针get函数返回的指针

如果我们主动释放掉get 函数获得的指针,那么智能指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果!

禁止用任何类型智能指针get函数返回的指针去初始化另外一个智能指针

shared_ptr< int > sp1(new int(10));
// 一个典型的错误用法 shared_ptr< int > sp4(sp1.get());

相关文章

  • C++11智能指针

    [C++11]智能指针 C++11包括了三种智能指针: shared_ptr weak_ptr unique_pt...

  • C++ 智能指针

    C++智能指针[https://zhuanlan.zhihu.com/p/54078587] C++11中智能指针...

  • 现代 C++:一文读懂智能指针

    智能指针 C++11 引入了 3 个智能指针类型: std::unique_ptr :独占资源所有权的指针。...

  • 智能指针和垃圾回收

    堆内存管理:智能指针与垃圾回收 显式内存管理 野指针 重复释放 内存泄漏 C++11 的智能指针 unique_p...

  • C++之智能指针

    本文主要总结C++11中的几种智能指针的原理,使用及实现方式。 I、上帝视角看智能指针 1、智能指针的引用是为了方...

  • Caffe 架构学习-底层数据1

    前言 shared_ptr智能指针 为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Point...

  • C++ 11 常用特性的使用经验总结(二)

    4、智能指针内存管理 在内存管理方面,C++11的std::auto_ptr基础上,移植了boost库中的智能指针...

  • 使用 C++11 智能指针时要避开的 10 大错误

    我很喜欢C++11的智能指针。在很多时候,对很多讨厌自己管理内存的人来说是天赐的礼物。在我看来,C++11的智能指...

  • std::unique_ptr使用

    1 引言 std::unique_ptr是c++11起引入的智能指针,为什么必须要在c++11起才有该特性,主要还...

  • 智能指针学习

    智能指针 介绍 为了更容易(同时也更安全的管)的使用动态内存,C++11提供了智能指针来管理new出来的内存sha...

网友评论

      本文标题:C++11 智能指针

      本文链接:https://www.haomeiwen.com/subject/cdoipdtx.html