文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

c++动态内存管理与智能指针的相关知识点

2024-04-02 19:55

关注

引言

程序使用三种不同的内存

静态内存:static成员以及任何定义在函数之外的变量栈内存:一般局部变量堆内存(自由空间):动态分配的对象

静态内存和栈内存中的变量由编译器产生和销毁,动态分配的对象在我们不再使用它时要由程序员显式地销毁

一、介绍

动态分配内存

使用智能指针:定义在头文件memory中

和容器一样,只能指针也是一种模板,需要给它传入一个参数来指定类型

二、shared_ptr类

声明shared_ptr:

shared_ptr<string> p1;	   //shared_ptr,可以指向string
shared_ptr<list<int>> p2;  //shared_ptr,可以指向list<int>

使用方式与普通指针一致,解引用返回它所指向的对象,在条件表达式中检查是否为空

//若p1不为空且指向一个空string
if(p1 && p1->empty()){
    *p1 = "hi";   //对p1重新赋值
}

make_shared函数

make_shared<typename>(arguments)

在动态内存中分配并初始化一个对象

返回指向此对象的shared_ptr指针

//指向一个值为42的int的shared_ptr
shared_ptr<int> p1 = make_shared<int>(42);
//指向一个值为"999"的string的shared_ptr
shared_ptr<string> p2 = make_shared<string>(3, '9');
//指向一个值为0的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>();

没有传入参数时,进行值初始化

auto p4 = make_shared<string>(); //p4指向空string

shared_ptr的拷贝和引用

每个share_ptr都有一个关联的计数器

当拷贝shared_ptr时,计数器会递增

当shared_ptr被赋予新值或者shared_ptr被销毁(如一个局部的shared_ptr离开其作用域),计数器会递减

当一个shared_ptr的计数器==0时,内存会被释放

auto r = make_shared<int>(42);
r = q; //给r赋值,使它指向另一个地址
	   //递增q指向的对象的引用计数
	   //递减r指向的对象的引用计数
       //如果计数器为0,自动释放

shared_ptr自动销毁所管理的对象…

和其他类一样,shared_ptr类型也有析构函数

shared_ptr的析构函数会

  1. 递减指针所指向的对象的引用计数
  2. 当对象的引用计数为0时,销毁对象并释放内存…shared_ptr还会自动释放相关联对象的内存

举例:

//factory返回一个share_ptr,指向一个动态分配的对象
shared_ptr<Foo> factory(T arg){
    //对arg的操作
    return make_shared<Foo>(arg);
}

void ues_factory(T arg){
    shared_ptr<Foo> p = factory(arg);
    //使用p
}
//p离开了作用域,由于引用计数由1减到0,对象被销毁,内存释放

如果有其他引用计数也指向该对象,则对象内存不会被释放掉

//factory和上述一致
//ues_factory返回shared_ptr的拷贝
void use_factory(T arg){
    shared_ptr<Foo> p = factory(arg);
    //使用p
    return p; //返回p的拷贝,此时递增了计数器,引用数为2
}//p离开作用域,对象计数器引用2-1=1,对象内存没有释放

return shared_ptr时,如果不是返回引用类型,则会进行拷贝,shared_ptr的计数器+1后-1,最终shared的计数器不变

由于在最后一个shared _ptr销毁前内存都不会释放,保证shared_ptr在无用之后不再保留就非常重要了。如果你忘记了销毁程序不再需要的shared_ptr,程序仍会正确执行,但会浪费内存。

share_ptr 在无用之后仍然保留的一种可能情况是,你将shared _ptr存放在一个容器中,随后重排了容器,从而不再需要某些元素。在这种情况下,你应该确保用erase删除那些不再需要的shared_ptr元素。

如果你将shared ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。

使用动态生存期的资源的类

程序使用动态内存的三种原因

容器类常出于第一种原因使用动态内存,在15章会看见出于第二种原因的例子,本节讨论第三种原因

先考虑这么一种情况:

我们要定义一个Blob类,当该类型的对象拷贝时,对象共享底层数据。

如b2 = b1时,b2,b1共享底层数据,对b2的操作也会印象到b1,且销毁b2时,b1的仍指向原数据

Blob<string> b1; //空Blob
{
    //新作用域
    Blob<string> b2 = {"a","an","the"};
    b1 = b2; //b1和b2共享数据
}//b2离开作用域,被销毁了,但b2的数据不能被销毁
//b1指向b2的原数据

应用举例:Blob类

定义Blob类

最终,我们希望将Blob定义为一个模板类,但现在我们先将其定义为StrBlob,即底层数据是vector<string>的Blob

class StrBlob{
public:
    //拷贝控制
    StrBlob();//默认构造函数
    StrBlob(initializer_list<string> il); //列表初始化
    StrBlob(const StrBlob& strb);
    //查询
    int size() const {return data->size();}
    bool empty() const {return data->empty();}
    //添加和删除元素
    void push_back(const string &t) {data->push_back(t);}
    void pop_back() {data->pop_back();}
    //访问元素
    string& front();
    string& back();
private:
    shared_ptr<vector<string>> data;
    //如果data[i]不合法,抛出异常
    void check(int i, const string &msg) const;
};

StrBlob的构造函数

StrBlob::StrBlob() : data(make_shared<vector<string>>())
        {cout<<"in StrBlob dafault"<<endl;};
StrBlob::StrBlob(initializer_list<string> il) :
    data(make_shared<vector<string>>(il))
    {cout<<"in StrBlob initializer_list"<<endl;}

元素访问成员函数

在访问时必须保证容器非空,定义check函数进行检查

void StrBlob::check(int i, const string& msg) const{
    if(i >= data->size())
        throw out_of_range(msg);
}

元素访问成员函数:

string& StrBlob::front(){
    //如果vector为空,check会抛出一个异常
    check(0, "front on empty StrBlob");
    return data->front();
}
string& StrBlob::back(){
    check(0, "back on empty StrBlob");
    return data->back();
}

StrBlob的拷贝、赋值和销毁

StrBlob使用默认的拷贝、赋值和析构函数对此类型的对象进行操作

当我们对StrBlob对象进行拷贝、赋值和销毁时,它的shared_ptr成员也会默认地进行拷贝、赋值和销毁

//由于data是private的
//在StrBlob中设置一个接口look_data
//look_data返回data的引用
class StrBlob{
public:
    //...
    shared_ptr<vector<string>>& look_data()
    {return data;} //返回引用,避免对象拷贝
private:
    //其余部分都不变
};

测试程序:

//测试程序
int main(){
    StrBlob b1;
    {//新作用域
        StrBlob b2 = {"first element","second element"};
        cout<<"before assignment : "
            <<b2.look_data().use_count()<<endl;
        b1 = b2;
        cout<<"after assignment  : "
            <<b2.look_data().use_count()<<endl;
    }//b2被销毁,计数器递减
    //b1仍指向b2的原数据
    cout<<b1.front()<<endl;
    //打印此时b1的计数器
    cout<<"b2 has been dstoryed : "
        <<b1.look_data().use_count()<<endl;
    return 0;
}

输出结果:

如果look_data用值返回,而不是引用返回,那么会存在拷贝【见6.2.2节笔记】,所有计数器的值会+1

三、直接管理内存

使用new分配内存

new和delete与智能指针不同,类对象的拷贝、赋值和销毁操作都不会默认地对动态分配的对象进行管理,无论是对象的创建还是销毁,都需要程序员显式地操作,在大型的应用场景中会十分复杂。

在熟悉C++拷贝控制之前,尽量只使用智能指针,而不是本节的方法管理动态内存

使用new动态分配和初始化对象

new type_name:返回一个指向该对象的指针

//pi指向一个动态分配,默认初始化的无名对象
int *pi = new int;
/)
{
	connection c = connect(&d);//打开链接
	unique_ptr<connection, decltype(end_connection)*>
		p(&c, end_connection);
	//使用链接
	//当f退出时(即使是由于异常而退出)
	//connection会调用end_connection正常退出
}

注意decltype(end_connection)返回一个函数类型,而函数类型不能作为参数,函数指针可以

所以要加上*表示函数指针
p(&c, end_connection)中,类似于数组名表示指针一样,函数名实际上就表示函数指针

所以也可写作p(&c, &end_connection),但没必要。【前一个&表示引用传递,后一个&表示取址得到指针】

总结

到此这篇关于c++动态内存管理与智能指针的文章就介绍到这了,更多相关c++动态内存管理与智能指针内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯