C++11动态内存与智能指针

一、动态内存

在C++中,动态内存的管理是用一对运算符完成的:new和delete,new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针,delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。

动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。

C++定义指针变量什么时候需要手动释放?

int*, char* ,这些定义是局部变量,存在于栈上,比如int *p;p在栈上,而且p的值也是栈的一个地址。但是当int *p = new int ;这时候,p这个变量是在栈上的。但是p的值是一个地址,这个地址是堆上的一个地址。如果不delete p;那么,这个地址会一直被占用着,不能被其他的对象所使用,所以我们用完这个地址,要把这个地址释放掉。因此栈的空间会自动释放,而堆里的空间必须手动释放。

二、智能指针

2.1 智能指针简介

智能指针是存储动态分配(堆)对象指针的类,用于生存周期控制,能够确保在离开指针所在作用域时,自动正确地销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数,每使用一次,内部的引用计数加1,每析构一次,内部引用计数减1,减为0时,删除所指向的堆内存。

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。

c++ 11 提供了3种智能指针:std::shared_ptr、std::unique_ptr、std::weak_ptr  ,使用时引用头文件   <memory>。

  • shared_ptr允许多个指针指向同一个对象
  • unique_ptr则“独占”所指向的对象
  • weak_ptr,它是一种弱引用,指向shared_ptr所管理的对象

2. 2 智能指针作用

因为智能指针是一个类当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

2.3 shared_ptr 共享的智能指针

std::shared_ptr 使用引用计数,每一个shared_ptr 的拷贝都指向相同的内存。在最后一个shared_ptr 析构时,内存才会被释放。

每个 shared_ptr 对象在内部指向两个内存位置:
1、指向对象的指针。
2、用于控制引用计数数据的指针。

共享所有权如何在参考计数的帮助下工作:
1、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1;
2、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存
多个 shared_ptr 对象可以共同托管一个指针 p,当所有曾经托管 p 的 shared_ptr 对象都解除了对其的托管时,就会执行delete p

#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
    int i;
    A(int n):i(n) { };
    ~A() { cout << i << " " << "destructed" << endl; }
};
int main()
{
    shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
    shared_ptr<A> sp2(sp1);       //A(2)同时交由sp2托管
    shared_ptr<A> sp3;
    sp3 = sp2;   //A(2)同时交由sp3托管
    cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
    A * p = sp3.get();      // get返回托管的指针,p 指向 A(2)
    cout << p->i << endl;  //输出 2
    sp1.reset(new A(3));    // reset导致托管新的指针, 此时sp1托管A(3)
    sp2.reset(new A(4));    // sp2托管A(4)
    cout << sp1->i << endl; //输出 3
    sp3.reset(new A(5));    // sp3托管A(5),A(2)无人托管,被delete
    cout << "end" << endl;
    return 0;
}

 输出结果:

2,2,2
2
3
2 destructed
end
5 destructed
4 destructed
3 destructed

2.3.1 智能指针的初始化方法

(1)构造函数初始化

std::shared_ptr<int> pointer(new int(1));//构造函数初始化智能指针
    std::shared_ptr<int> pointer1 = pointer;//智能指针给智能指针赋值
    std::cout << "pointer = " << *pointer << std::endl;
    std::cout << "pointer1 = " << *pointer1;


 

(2)std::make_shared 初始化

    std::shared_ptr<int> p3 = std::make_shared<int>(1);
    std::cout << "p3 = " << *p3 << std::endl;

(3)reset 初始化

使用reset 方法初始化智能指针

std::shared_ptr<int> pointer;
pointer.reset(new int(1));

2.3.2 获取智能指针

当需要获取智能指针时,可以通过get方法来返回原始指针。

    std::shared_ptr<int> p3 = std::make_shared<int>(1);
    std::cout << "p3 = " << *p3 << std::endl;
    int *ptr = p3.get();
    std::cout << "ptr = " << *ptr << std::endl;

 使用智能指针开辟空间

shared_ptr<char> queryResult(new char[300], [](char* data) { delete[] data; });
char *respData = queryResult.get();

 2.3 .3 指定删除器

智能指针初始化可以指定删除器。

如下所示,当p 的引用计数为0时,自动调用删除器DeleteIntPtr 来释放对象的内存。

void DeleteIntPtr(int* p)
{
    delete p;
    std::cout << "delete p " << std::endl;
}
int main()
{
    std::shared_ptr<int> pointer(new int(1),DeleteIntPtr);
    
}

 也可以用  lambda 表达式的形式

std::shared_ptr<int> pointer1(new int(1), [](int* p) {delete p; });

2.3.4 错误用法

 错误用法:

(1)不能将一个原始的指针直接赋值给一个智能指针

 std::shared_ptr<int> p = new int(1);//错误用法

(2) 不能用一个原始指针初始化多个shared_ptr 

    int* ptr = new int;
    shared_ptr<int> p1(ptr);
    shared_ptr<int> p2(ptr);//错误用法,不能用一个原始指针初始化化多个 shared_ptr

 (3)要避免循环引用,循环引用会导致内存泄露

struct A;
struct B;
struct A
{
    std::shared_ptr<B> bptr;
    ~A(){ cout << "A is deleted!" << endl; }
};
struct B
{
    std::shared_ptr<A> aptr;
    ~B(){ cout << "B is deleted!" << endl; }
};
int main()
{
    std::shared_ptr<A> ap(new A);
    std::shared_ptr<B> bp(new B);
    ap->bptr = bp;
    bp->aptr = ap;
    return 0;  
}

 循环引用导致 ap和bp 的引用计数为2,在离开作用域之后,ap和bp 的引用计数减为1,并不会减为0,导致两个指针都不会被析构,产生了内存泄露。

2.3.5 自己编写一个智能指针

C++中智能指针及其类型的介绍

2.3.6 判断智能指针是否非空

访问智能指针包含的裸指针则可以用 get() 函数。由于智能指针是一个对象,所以if (my_smart_object)永远为真,要判断智能指针的裸指针是否为空,需要这样判断:if (my_smart_object.get())。 

2.4 unique_ptr 独占的智能指针

2.4.1 unique_ptr 基本概念

unique_ptr 是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr 赋值给另外一个unique_ptr

可以通过std::move 来转移其他的unique_ptr 。

 std::unique_ptr<int> myPtr(new int(1));//okey

 使用std::move 转移,如下所示,转移之后 myPtr 不能指向原来的对象。

    std::unique_ptr<int> myPtr(new int(1));//okey
    std::unique_ptr<int> myPtr1 = std::move(myPtr);//使用std::move 来转移到其他的unique_ptr

错误用法:

std::unique_ptr<int> myPtr1 = myPtr;//错误用法,unique_ptr 智能指针不能赋值给另外一个 unique_ptr

 2.4.2 share_ptr 和 unique_ptr 的选择:

如果希望只有一个智能指针管理资源或者管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用share_ptr。

2.5 weak_ptr 弱引用的智能指针

弱引用指针weak_ptr 是用来监视shared_ptr 的,不会使引用计数加1,它也不管理shread_ptr 内部的指针,主要用来监视shared_ptr 的生命周期。

(1)通过use_count() 来获得当前观测资源的引用计数。

(2)通过expired() 方法来判断所观测的资源是否已经被释放。

(3)通过lock 方法来获取所监视的shared_ptr。

参考:

【1】C++11 - std::shared_ptr初始化的几种方式:C++11 - std::shared_ptr初始化的几种方式_HW140701的博客-CSDN博客_shared_ptr初始化

【2】C++ 智能指针 shared_ptr 详解与示例: c++11智能指针(一) shared_ptr - 简书

【3】C++11 shared_ptr(智能指针)详解: C++11 shared_ptr(智能指针)详解

【4】std::shared_ptr:  shared_ptr - C++ Reference

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值