C++中的内存管理(二)

文章目录

某些应用需要一次性为很多对象分配内存的功能。C++中提供了两种一次分配一个对象数组的功能:newallocator,其中后者允许将分配和初始化分离,提供更好的性能和更灵活的内存管理能力。

大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更为简单,更不容易出现内存管理错误并且可能拥有更好的性能。

使用容器的类可以使用默认的拷贝、赋值和析构操作。分配动态数组的类必须定义自己版本的操作,在拷贝、复制以及摧毁对象时管理所管理的内存。

new和数组

cpp 复制代码
//get_size()即使为0,也是合法的。但是不能解引用,类似尾后指针
int *pia = new int[get_size()]; 

typedef int arrT[42];
int *p = new arrT; //分配一个42个int的数组,p指向第一个int

虽然我们通常称new T[]分配的内存为动态数组,但这种叫法有些误导。我们得到的并非要给数组类型的对象,而是得到一个数组元素类型的指针(没有数组的维度)。因此不能对动态数组调用begin和end,也不能使用范围for语句来处理动态数组中的元素。

初始化动态分配对象的数组

默认情况下,new分配的对象都是默认初始化的。可在后面跟一个()进行值初始化:

cpp 复制代码
int *pia = new int[10]; //10个未定义
int *pia2 = new int[10](); //10个0
string *psa = new string[10]; //10个空string
string *psa2 = new string[10](); //10个空string

在新标准中,还可以使用初始化器{}

cpp 复制代码
int *pia3 = new int[10]{0, 1, 2}; //剩余元素进行值初始化
string *psa3 = new string[10]{"a", "an", string(3, 'x')};

释放动态数组

cpp 复制代码
delete p; //p为动态对象或空
delete [] pa; //pa为一个动态分配的数组或空,逆序摧毁数组中元素

智能指针和动态数组

  • unique_ptr
cpp 复制代码
//无需提供自定义删除器,类型为T[]
unique_ptr<int []> up(new int[10]);
up.release(); //自动使用delete []摧毁其指针

//unique_ptr可以使用下标运算符
for(size_t i = 0; i != 10; ++i)
	up[i] = i;
  • shared_ptr
cpp 复制代码
//不直接支持管理动态数组,类型为T,必须提供删除器
shared_ptr<int> sp(new int[10], [](int *p){delete [] p;});

//不支持下表访问
for(size_t i = 0; i != 10; ++i)
	*(sp.get() + i) = i;

allocator

new在灵活性上有一定的局限性。其将内存分配和对象构造组合在了一起。delete同理。当分配一块大内存时,通常计划按需构造对象,因此需要将内存分配和对象构造分离。

cpp 复制代码
string *const p = new string[n];
string s;
string *q = p;
while(cin >> s && q != p + n)
	*q++ = s;
const size_t = q - p;
delete [] p;
  • new表达式分配并初始化了n个string。但是,我们可能并不需要n个string;
  • 对于确实需要使用到的对象,也在初始化后立即赋予了新值。每个使用到的元素都被赋值了两次;
  • 对于没有默认构造函数的类,不能使用动态数组。

allocator类

当一个allocator对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对齐位置:

cpp 复制代码
allocator<string> alloc; //可以分配string的对象
auto const p = alloc.allocate(n); //分配n个未初始化的string

初始化

cpp 复制代码
auto p = q;
alloc.construct(q++); //string默认构造函数构建q,q++
alloc.construct(q++, 10, 'c'); //q指向"ccc..."
alloc.construct(q++, "hi"); //q指向"hi"

cout << *p << endl; //ok
cout << *q << endl; //err,当前q还未构造

摧毁对象

当我们使用完对象之后,必须对每个构造的元素调用destroy来摧毁他们。值得注意的是,只有真正构造了的元素才能使用该操作。

cpp 复制代码
while (q != p)
	alloc.destroy(--q);

归还内存

一但元素被摧毁之后,就可以重新使用这部分内存,也可以通过deallocate来归还内存:

cpp 复制代码
alloc.deallocate(p, n);

传递给deallocate的指针不能为空,必须指向alloc分配的内存,n也必须和分配的一致。

拷贝和填充未初始化内存的算法

假定有一个int的vector,希望将其内容拷贝到动态内存中。我们可以分配一块必vector元素所占空间大一倍的动态内存,然后将袁vector的元素拷贝到前一半空间,多一半用一个值填充:

cpp 复制代码
auto p = alloc.allocate(vi.size() * 2);
auto q = uninitialized_copy(vi.begin(), vi.end(), p); //返回最后一个构造元素之后的指针
uninitialized_fill_n(q, vi.size(), 42);
相关推荐
持力行6 小时前
从C struct到C++中的class
c语言·c++
meilindehuzi_a7 小时前
从零理解并实现 RAG:用 LangChain.js 构建语义检索问答系统
开发语言·javascript·langchain
风流 少年8 小时前
Julia
开发语言·julia
江畔柳前堤8 小时前
GO01-Go 语言与主流编程语言深度对比
开发语言·人工智能·后端·微服务·云原生·golang·go
:-)9 小时前
算法-归并排序
java·开发语言·数据结构·算法·排序算法
GIS阵地9 小时前
QgsRasterDataProvider 完整详解(QGIS 3.40.13 C++)
开发语言·c++·qt·开源软件·qgis
yaoxin52112310 小时前
462. Java 反射 - 获取声明类与封闭类
java·开发语言·python
阿里嘎多学长10 小时前
2026-07-14 GitHub 热点项目精选
开发语言·程序员·github·代码托管
charlie11451419111 小时前
Cinux: 为大内核铺路
开发语言·c++·操作系统·现代c++
2501_9142459311 小时前
C语言设计模式详解:从理论到实践的完整指南
c语言·开发语言·设计模式