动态内存与智能指针

本文介绍了C++中智能指针的基本概念,包括shared_ptr的使用方法及其如何管理动态分配的对象。探讨了智能指针如何避免内存泄漏等问题,并通过实例说明了如何利用shared_ptr实现数据共享。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们先来看一些对象的生存期。全局对象在程序启动时分配,在程序结束时销毁。局部static对象在第一次使用前分配,在程序结束时销毁。局部自动对象,在进入其定义所在的程序块儿时被创建,离开块时销毁。即,它们都是由编译器自动创建与销毁。
而动态分配的对象的生存期与它们在哪里创建的无关,只有当显式地被释放时,这些对象才销毁。

在C++中,动态内存的管理是通过一对运算符来完成的:

new:在动态内存为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化。
delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
动态内存的使用很容易出问题,这里列举如下几点:
缓冲区溢出
空悬指针/野指针
重复释放: 释放已经被释放过了的内存
内存泄漏: 忘记释放内存
不配对的new[]/delete

先列这几条,等先了解智能指针的概念以后在来看怎么用智能指针解决这些问题。

为了更安全(同时也更容易)地使用动态内存,标准库提供了智能指针类型来管理动态对象。智能指针的行为类似于常规指针,重要的区别是它负责自动释放所指向的对象。

我们首先来看一下shared_ptr类,该类型在memory头文件中。

shared_ptr类

智能指针也是模板。因此在创建一个智能指针时,必须提供它可以指向的类型。

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

默认初始化的智能指针保存着一个空指针。类似与普通指针,解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果是检测它是否为空。

//如果p1指向一个空string,解引用p1,将一个新值给string
if(p1 && p1->empty()){
   *p1 = "hi";
}

最安全的分配和使用动态内存的方法是调用标准库函数make_shared。定义在头文件memory中。该函数在动态内存中分配一个对象并初始化,返回指向此对象的shared_ptr。make_shared也是一个模板,使用方法如下:

//指向一个值为42的int对象的shared_ptr
shared_ptr<int> p3 = make_shared<int>(42);

//p4指向一个"9999999999"的string对象
shared_ptr<string> p4 = make_shared<string>(10,'9');

//p5指向一个值初始化的int,即值为0
shared_ptr<int> p5 = make_shared<int>();

make_shared用其参数来构造给定类型的对象。例如,调用make_shared时传递的参数必须与string的某个构造函数相匹配。通常用auto定义一个对象来保存make_shared的结果:

//p6指向一个动态分配的vector<string>
auto p6 = make_shared<vector<string>>();
shared_ptr的拷贝和赋值

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

auto p = make_shared<int>(42);   //p指向的对象只有p一个引用者
auto q(p);                       //pq指向相同的对象,此对象有两个引用者。

可以认为每个shared_ptr都有一个关联的计数器(引用计数)。无论何时,拷贝一个shared_ptr,计数器都会递增。例如:

用一个shared_ptr初始化另外一个shared_ptr
将一个shared_ptr作为一个参数传递给一个函数
当一个shared_ptr作为一个函数的返回值

相对的,给一个shared_ptr赋予一个新值或者shared_ptr被销毁时,计数器就会递减。
一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

auto r = make_shared<int>(42);
r = q;  //q指向的对象的引用计数递增。
        //r原来指向的对象的引用计数递减。
        //r原来指向的对象没有了引用者,会自动释放。

当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象,它是通过析构函数来完成销毁动作的。shared_ptr的析构函数会递减它所指向的对象的引用计数,如果引用计数边为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

对于一块内存,shared_ptr类保证只要有任何shared_ptr对象引用它,它就不会被释放掉。

但是,如果你忘记了销毁程序不需要的shared_ptr,程序仍会正确执行,但会浪费内存。一种可能的情况是,将shared_ptr放在一个容器里,当不需要某些元素时,应该确保用erase删除那些不需要的shared_ptr元素。

程序使用动态生存期的资源的类 出于以下三种原因之一:

程序不知道自己需要使用多少对象(空间)例如,容器类
程序不知道所需对象的准确类型
程序需要在多个对象间共享数据

下面是一个需要在多个对象间共享数据的例子:
我们定义一个strblob类,保存一组元素。与容器不同,我们希望strblob对象的不同拷贝之间共享相同的元素。我们可以用一个vector来保存元素,但是,不能在一个strblob对象内直接保存vector,因为一个对象的数据成员在对象销毁时也会被销毁。为了实现strblob对象共享相同的底层数据,我们可以将vector保存在动态内存中,然后为每个strblob设置一个shared_ptr来管理动态内存分配的vector。这个shared_ptr的成员将记录有多少个shared_ptr共享相同的vector,并在vector的最后一个使用者被销毁时释放vector。


#ifndef _STRBLOB_H
#define _STRBLOB_H
#include<string>
#include<memory>
#include<list>
#include<vector>

using namespace std;
class strblob
{
public:
    strblob();
    strblob(std::initializer_list<std::string> s);
    void print(){
        for(auto v : *data_){
            std::cout << v << std::endl; 
        }
    }

private:
    std::shared_ptr<std::vector<std::string>> data_;
};

strblob::strblob():data_( make_shared<vector<string>>() ){}
strblob::strblob(initializer_list<string> s):data_(make_shared<vector<string>>(s)){}
#endif

下面是一个测试用例

#include"strblob.h"
int main(int argc,char *argv[])
{
    std::initializer_list<std::string> s = {"hello"};

    strblob A;       //使用不带参数的构造函数
    strblob B(s);    //使用带参数的构造函数

    A.print();
    B.print();

    std::cout << std::endl;
    //B对象的shred_ptr指向的对象的引用计数递增,A的递减并且释放A的shared_ptr指向的对象
    A = B;    //使用默认赋值
    A.print();
    B.print();
    return 0;
}
其他shared_ptr操作:

我们可以用reset来将一个新的指针赋予一个shared_ptr

p.reset();     //若p是唯一指向对象的shared_ptr,reset会释放此对象。
p.reset(q);    //若传递了可选参数内置指针q,会令p指向q,否则会将p置为空。
p.reset(q,d);  //如果还传递了参数d,将会调用d而不是delete来释放q
weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放,因此weak_ptr的名字抓住了这种智能指针“弱”共享对象的特点。

当我们创建一个weak_ptr时,要用一个shared_ptr来初始化它:

auto p = make_shared<int>(42);
weak_ptr<int> wp(p);  //wp弱共享p; p的引用计数未改变。

由于waek_ptr指向的对象可能不存在,因此我们不能直接使用weak_ptr来访问对象,而必须调用lock()。此函数检查weak_ptr指向的对象是否仍存在,如果存在,lock返回一个指向共享对象的shared_ptr。与任何其他shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。

if(auto np = wp.lock()){
//进入if条件证明wp指向的对象存在,在这里np与p共享对象
}
内容概要:本文介绍了多种开发者工具及其对开发效率的提升作用。首先,介绍了两款集成开发环境(IDE):IntelliJ IDEA 以其智能代码补全、强大的调试工具和项目管理功能适用于Java开发者;VS Code 则凭借轻量级和多种编程语言的插件支持成为前端开发者的常用工具。其次,提到了基于 GPT-4 的智能代码生成工具 Cursor,它通过对话式编程显著提高了开发效率。接着,阐述了版本控制系统 Git 的重要性,包括记录代码修改、分支管理和协作功能。然后,介绍了 Postman 作为 API 全生命周期管理工具,可创建、测试和文档化 API,缩短前后端联调时间。再者,提到 SonarQube 这款代码质量管理工具,能自动扫描代码并检测潜在的质量问题。还介绍了 Docker 容器化工具,通过定义应用的运行环境和依赖,确保环境一致性。最后,提及了线上诊断工具 Arthas 和性能调优工具 JProfiler,分别用于生产环境排障和性能优化。 适合人群:所有希望提高开发效率的程序员,尤其是有一定开发经验的软件工程师和技术团队。 使用场景及目标:①选择合适的 IDE 提升编码速度和代码质量;②利用 AI 编程助手加快开发进程;③通过 Git 实现高效的版本控制和团队协作;④使用 Postman 管理 API 的全生命周期;⑤借助 SonarQube 提高代码质量;⑥采用 Docker 实现环境一致性;⑦运用 Arthas 和 JProfiler 进行线上诊断和性能调优。 阅读建议:根据个人或团队的需求选择适合的工具,深入理解每种工具的功能特点,并在实际开发中不断实践和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值