《c++ primer笔记》第十二章 动态内存

目前写的程序都只使用过静态内存和栈内存。静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁,对于栈对象,仅在其定义的程序块运行时才存在:static对象在使用之前分配,在程序结束时销毁

​ 除了静态内存和栈内存,每个程序还拥有一个内存池–自由空间或堆。程序用堆来存储动态分配的对象:在程序运行时分配的对象。 动态对象的生存期由程序来控制,也就是当动态对象不再使用时,必须显示地销魂它们。

3.1动态内存与智能指针

new:在动态内存中为对象分配空间并返回一个指向该对象的指针。delete:接受一个动态对象的指针,销毁该对象,释放与之关联的内存。忘记释放内存会导致内存泄露,而在尚有指针引用内存的情况下释放会导致产生引用非法内存的指针

智能指针的出现是为了更安全的管理动态对象,类似常规指针,区别在于它会负责自动释放所指向的对象。新标准库提供了两种智能指针:1)shared_ptr:允许多个指针指向同一个对象;2)unuque_ptr:独占指向的对象。此外还有一个weak_ptr,指向shared_ptr所管理的对象。三种类型定义在memory中。

3.1.1shared_ptr类

​ 智能指针也是模板,默认初始化的智能指针中保存一个空指针,使用方式与普通指针类似,解引用一个智能指针返回它所指向的对象。

image-20230323184241706

make_shared函数

​ 此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr

shared_ptr<int> p3 = make_shared<int>(42);
shared_ptr<string> p4 = make_shared<string>(10,'9');
shared_ptr<int> p5 = make_shared<int>(); // 值为0

auto p6 = make_shared<vector<string>>(); // 指向一个动态分配的空vector<string> 

类似于顺序容器的emplace成员,make_shared用其参数来构造给定类型的对象,比如调用make_shared<string>传递的参数必须与string某一个构造函数相匹配。

shared_ptr的拷贝和赋值

​ 当进行拷贝或者赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象, 可以理解它内部有一个计数器(引用计数)。

auto p = make_shared<int>(42); // 42有一个引用者
auto q(p); // 现在42有两个引用者

一旦一个shared_ptr的计数器变为0就会被自动释放

auto r = make_shared<int>(4);
r = 1; // 4已经没有对象指向它,计数器值为0,会自动释放

shared_ptr被销毁时是通过一个特殊的成员函数:析构函数完成销毁工作。==shared_ptr的析构函数会递减它所指向的对象的引用计数,如果引用计数变为0,析构函数就会销毁对象。==此外,当动态对象不再被使用时,shared_ptr类会自动释放动态对象。如下面一个例子:

shared_ptr<Foo> factory(T arg) {
	...
	return make_shared<Foo>(arg);
}

factory返回一个shared_ptr,所以我们可以确保它分配的对象会在恰当的时刻被释放。

void use_factory(T arg) {
	shared_ptr<Foo> p = factory(arg);
}

可以看到p是一个局部变量,当它离开了作用域,指向的内存就会被自动释放掉(要先递减判断计数器是否为0,如果为0 就释放)。

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

​ 程序使用动态内存的三个原因:

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

一般来说,由一个容器分配的元素只有当容器存在时才会存在,但是某些类分配的资源具有与原对象相独立的生存期。下面看一个例子:StrBlob,我们想达到拷贝对象与原对象共享一个底层数据,所有当我们删除其中一个对象时,里面的数据是不能被销毁的。

定义StrBlob

class StrBlob {
public:
	typedef std::vector<std::string>::size_type size_type; // size_type = vector<string>
	StrBlob(); // 默认构造函数
	StrBlob(std::initializer_list<std::string> il); // 列表初始化构造函数
	size_type size() const { return data->size(); } // 返回一个vector<string>的元素大小
	bool empty() const { return data->empty(); } // 检查是否为空
    void push_back(const std::string &t) {data->push_back(t);} // 添加元素
    void pop_back(); // 删除元素
    
    std::string &front();
    std::string &back();
private:					  
	std::shared_ptr<std::vector<std::string>> data;
	void check(size_type i, cosnt std::string &msg) const; // 在对容器的元素进行操作时,检查元素是否存在
}

StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) : 
	data(make_shared<vector<string>>(il)) {}

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

3.2动态数组

3.2.1new和数组

​ 使用new分配一个对象数组,需要定义分配的数量,new分配要求数量的对象并返回指向第一个对象的指针。

int *pia = new int[get_size()]; // pia指向第一个int

==分配一个数组会得到一个元素类型的指针,==由于分配的内存并不是一个数组类型,因此不能对动态数组调用beginend,同样也不能用范围for语句来处理动态数组中的元素。

动态分配一个空数组是合法的

​ 如果下面代码中的get_size()返回零,任然可以正常工作,虽然不能创建一个大小为0的静态数组对象,但是当n = 0时,调用new[n]是合法的。

size_t n = get_size();
int *p = new int[n];
for(int *q = p; q != p + n; ++ q)
    
char arr[0]; // 错误
char *cp = new char[0]; // 正确,但是cp不能解引用

释放动态数组

​ 动态数组被释放时,数组中的元素按照逆序销毁。

delete [] pa; // []告诉编译器它指向一个对象数组的第一个元素

智能指针和动态数组

​ 标准库提供了一个可以管理new分配的数组的unique_ptr版本。

unique_ptr<int[]> up(new int[10]);
up.release(); // 自动调用delete[]销毁其指针

image-20230328194534102

unique_ptr不同,shared_ptr不直接支持管理动态数组,如果需要其管理一个动态数组,必须提供自己定义的删除器。

shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; }); // 使用lambda
sp.reset();

shared_ptr不直接支持动态数组管理这一特性会影响访问数组中的元素

for(size_t i = 0; i != 10; ++ i)
	*(sp.get() + i) = i; // 使用get获取一个内置指针。

shared_ptr未定义下标运算符,而且智能指针类型不支持指针算术运算,为了访问数组中的元素,必须用get获取一个内置指针。

3.2.2allocator类

new将内存分配和对象构造组合在了一起,delete将对象析构和内存释放组合在了一起,造成了灵活性上的局限。一般情况下,将内存分配和对象构造组合在一起可能会导致不必要的浪费

string *const p = new string[n]; // 构造n个空string
string s;
string *q = p; // q指向第一个string
while( cin >> s && q != p + n)
	*q ++ = s; // 输入s赋值到q
const size_t size = q - p; // 获得输入string的数量
delete []p; // 释放p

在上面代码中,new表达式分配并初始化n个string,但是我们可能用不完n个string,此外对于那些确定要用的对象,我们在初始化之后立即赋予了它们新值,每个使用的元素都被赋值了两次,第一次在默认初始化,随后是在赋值时,更重要的是那些没有默认构造函数的类不能动态分配数组。

allocator类可以将内存分配和对象构造分离,提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。使用allocator对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对齐位置

allocator<string> alloc; 
auto const p = alloc.allocate(n); // 分配n个未初始化的string

image-20230328201337547

allocator分配未构造的内存

construct成员函数接受一个指针和零个或多个额外参数,在给定的位置构造一个元素,额外参数用来初始化构造的对象。为了使用allocate返回的内存,必须使用construct构造对象,使用未构造的内存,行为是未定义的

auto q = p; / q指向最后构造的元素之后的位置
alloc.construct(q ++); // *q是一个空字符串
alloc.construct(q ++, 10, 'c'); // *q为ccccccccc
alloc.construct(q ++, "hi"); // *q为hi 

当用完对象后,必须对每个构造的元素调用destroy来销毁它们。

while (q != p)
	alloc.destroy(--q);

在元素被销毁后,就可以重新使用这部分内存来保存其他string,或者归还给系统。

alloc.deallocate(p, n)

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

image-20230330100741662

stroy`来销毁它们。

while (q != p)
	alloc.destroy(--q);

在元素被销毁后,就可以重新使用这部分内存来保存其他string,或者归还给系统。

alloc.deallocate(p, n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

madkeyboard

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值