当前位置: 首页 > ai >正文

C++中的智能指针(1):unique_ptr

一、背景

普通指针是指向某块内存区域地址的变量。如果一个指针指向的是一块动态分配的内存区域,那么即使这个指针变量离开了所在的作用域,这块内存区域也不会被自动销毁。动态分配的内存不进行释放则会导致内存泄漏。

如果一个指针指向的是一块已经被释放的内存区域,那么这个指针就是悬空指针。使用悬空指针会造成不可预料的后果。

如果我们定义了一个指针但未初始化使其指向有效的内存区域时,这个指针就成了野指针。使用野指针访问内存一般会造成段错误,即segmentation fault.

注:Segmentation fault:当程序试图访问未被分配的内存或无权访问的内存区域时,操作系统强制终止程序产生的错误。常见原因有:1.解引用空指针(上面所提到的);2.访问已释放的内存(悬空指针);3.数组越界;4.栈溢出(无限递归或过大的局部变量)。

使用智能指针可以有效避免上述错误的发生。智能指针是一个对象,它封装了一个指向另一个对象的指针。当智能指针对象离开了作用域后,会被自动销毁。销毁过程中会调用析构函数删除所封装的对象

二、unique_ptr

unique_ptr与它所管理的动态对象是一对一的关系,换言之,不能有两个unique_ptr对象同时指向相同的一块地址。

创建一个unique_ptr对象有两个方法:

unique_ptr<T> ptr1(new T(参数))
unique_ptr<T> ptr1= make_unique<T>(参数)

这里的T是一个类名。方法一:在构造函数中传入new分配的类对象。方法二:使用make_unique函数,它的参数是类T的构造函数的参数。

我们来看一个例子:

class Person
{
private:string name;int age;public:Person(string m_name, int m_age) :name(m_name), age(m_age){}~Person(){cout << "对象"<<name<<"被释放" << endl;}void check(){if (age < 18){cout << name << "的年龄为" << age << ",小于18岁" << endl;}else{cout << name << "的年龄为" << age << ",是成年人" << endl;}}
};int main()
{unique_ptr<Person>ptr1(new Person("比企谷", 17));unique_ptr<Person>ptr2 = make_unique<Person>("伊蕾娜", 18);ptr1->check();ptr2->check();return 0;
}

在main函数中我们定义了两个unique_ptr指针ptr1和ptr2,分别封装了两个动态创建的Person对象“比企谷”和“伊蕾娜”。由于智能指针重载了间接成员运算符和解引用运算符,它们会返回智能指针所包含对象的指针或者引用,因此可以像使用普通指针那样使用智能指针。例如上面的ptr1->check(),直接调用了Person对象的成员check.智能指针离开作用域后自动销毁,并调用析构函数释放指向的Person对象。

需要注意,下面三种情况也会删除当前所管理的对象:

ptr1=nullptr;
ptr1=move(ptr2);
ptr1.reset(new Person("雪之下雪乃",17));

上文说过,unique_ptr对所管理的资源具有独占性,所以unique_ptr的一个重要特性就是不能被拷贝也不能被赋值。

下面这段代码会在编译时出错:

unique_ptr<Person>ptr3=ptr2;

unique_ptr的类定义中没有这个拷贝构造函数。这样就保证了unique_ptr对象封装的指针不能和其它unique_ptr共用。但是上文提到了,我们可以对unique_ptr所管理对象的所有权进行转移,即:使用move函数。

unique_ptr<Person>ptr3=move(ptr2)

这样ptr3就拥有了原来ptr2所封装的指针的控制权。此时ptr2只包含了一个空指针,可以使用ptr3来访问它所封装的对象的成员。由于ptr2只包含了一个空指针,如果还使用ptr2访问成员,会出现segmentation fault.

再来看一个例子:

class energy
{
public:energy(){cout << "能量已充满" << endl;}~energy(){cout << "能量值为0" << endl;}
};unique_ptr<energy> fill()
{return unique_ptr<energy>(new energy());
}void consume(unique_ptr<energy>Energy)
{cout << "能量被消耗了" << endl;
}int main()
{cout << "开始" << endl;auto eng = fill();consume(eng);//错误的cout << "结束" << endl;
}

为什么consume(eng)是错误的?因为没有相应的拷贝构造函数,所以不能直接传值。正确方法如下:

consume(move(eng));

使用move函数将所有权转移给新的unique_ptr对象。

结果如下:

可以看到,这个engery对象被自动释放。

再来看一个例子:

struct Packet
{long m_id;char data[1000];Packet(long id) :m_id(id) {};
};struct Compare
{bool operator()(const Packet& a, const Packet& b){return a.m_id < b.m_id;}
};void sort_value_vector(int n)
{vector<Packet>vec;for (int i = 0; i < n; i++){vec.push_back(Packet(rand() % n));}sort(vec.begin(), vec.end(), Compare());
}

对于Packet这种较大的对象,排序意味着需要大量数据的移动复制。因为一个Packet对象大概是1008个字节,每交换两个Packet对象就需要复制1008字节的数据。我们可以将容器中的对象改成指针,这样排序的时候只涉及到指针值的复制。(交换两个指针只需复制4/8个字节)

struct Packet
{long m_id;char data[1000];Packet(long id) :m_id(id) {};
};struct Compare
{bool operator()(const Packet* pa, const Packet* pb){return pa->m_id < pb->m_id;}
};void sort_value_vector(int n)
{vector<Packet*>vec;for (int i = 0; i < n; i++){vec.push_back(new Packet(rand() % n));}sort(vec.begin(), vec.end(), Compare());
}

但是对于指针,需要单独进行维护。删除替换时需要释放不再使用的指针对象。不妨把容器中的指针换成unique_ptr,则不仅获得了普通指针的性能,还实现了内存资源的自动释放。

struct Packet
{long m_id;char data[1000];Packet(long id) :m_id(id) {};
};struct Compare
{template<template<typename> typename ptr>bool operator()(const ptr<Packet>&pa, const ptr<Packet>&pb){return pa->m_id < pb->m_id;}
};template<typename ptr>
void sort_value_vector(int n)
{vector<ptr>vec;for (int i = 0; i < n; i++){vec.push_back(new Packet(rand() % n));}sort(vec.begin(), vec.end(), Compare());
}

http://www.xdnf.cn/news/15114.html

相关文章:

  • 《汇编语言:基于X86处理器》第7章 整数运算(2)
  • 星云穿越与超光速飞行特效的前端实现原理与实践
  • 上位机知识篇---Linux软硬链接
  • 用 ELK+Filebeat 提高50%问题排查效率,这套方案实测有效!
  • cnpm exec v.s. npx
  • Shader面试题100道之(81-100)
  • python之set详谈
  • LeetCode经典题解:128、最长连续序列
  • TCP服务器与客户端三种方法实现
  • Linux权限的概念
  • SM712.TCT Semtech TVS二极管——电子设备的终极电路守护
  • DNS(Domain Name System,域名系统)
  • 计算机毕业设计ssm晋中大学城校园论坛 SSM大学城学生社区互动管理平台 JavaWeb高校校园信息交流与服务系统
  • java底层的native和沙箱安全机制
  • 系统思考:多元胜过能力
  • 鸿蒙 Secure Boot 全流程解析:从 BootROM 到内核签名验证的实战指南
  • 2025 年值得尝试的 6 大内容管理系统 (CMS)
  • 【实用IP查询工具】IP数据云-IP地址查询离线库使用方案
  • 【操作系统】Linux 中的 exec 命令
  • RK3566/RK3568 Android11 CAN开发(内核配置+测试验证+安卓app开发)
  • STM32F103之存储/启动流程
  • HarmonyOS基础概念
  • 【TCP/IP】17. 移动 IP
  • Swift 解 LeetCode 324:一步步实现摆动排序 II,掌握数组重排的节奏感
  • 雷达遥感星座微波射频组件抗辐照MCU的选型与实践
  • 【JMeter】接口加密
  • 【JMeter】调试方法
  • 学弟让我帮忙写一个学生管理系统的后端,我直接上科技
  • [大模型问数]实现大模型调用MYSQL(03)【MCP笔记】
  • Webview 中可用的 VS Code 方法