1、动态分配的对象的生存期与它们在哪里创建是无关的,只有当显示的被释放时,这些对象才会销毁。
2、静态内存,堆和栈内存。
2.1、静态内存是用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。
2.2、栈内存用来保存定义在函数内的非static对象。
2.3、程序用堆来存储动态内存。
2.4、分配在静态或堆内存中的对象由编译器自动创建和销毁。
3、C++中动态内存的管理是通过一对运算符来完成的。
new | 在动态内存中为对象分配空间并返回一个指向该对象的指针 |
delete | 接受一个动态对象的指针,销毁该对象,并释放与之关联的内存 |
注意:动态内存的使用容易出现问题。有时我们会忘记释放内存。,这种情况下就会产生内存泄漏。
4、为了更容易使用动态内存,新的标准库提供了两种智能指针来管理动态对象。
类型 | |
---|---|
shared_ptr | 允许多个指针指向同一个对象 |
unique_ptr | 独占所指向的对象 |
weak_ptr | 一种弱引用,指向shared_ptr所管理的对象 |
这三种类型都定义在memory头文件中。
1.1、程序不知道自己需要使用多少对象。
1.2、程序不知道所需对象的准确类型。
1.3、程序需要在多个对象之间共享数据。
shared_ptr<string> p1 //shared_ptr,可以指向string
shared_ptr<list<int>> p2 //shared_ptr可以指向int的list
shared_ptr<int>p3(new int (42)); //p2指向一个值为42的int
//我们不能进行内置指针到智能指针间的隐式转换。因为智能指针默认使用delete释放它所关联的对象。
默认初始化的智能指针中保存着一个空指针。
shared_ptr<~> sp | 空智能指针,可以指向任意类型的对象 |
p | 将p用作对象,若p指向一个对象,则为true |
*p | 解引用p,获得他指向的对象 |
p->mem | 等价于(*p).mem |
p.get() | 返回p中保存的指针 |
swap(p,q) | 交换p,q的指针 |
p.swap(q) | 等价于swap(p,q) |
make_shared<~>(args) | 返回一个shared_ptr |
shared_ptr<~>p(q) | p是shared_ptr q的拷贝 |
p=q | p和q都是shared_ptr,用保存的指针必须可以相互转换。此操作会递减p的引用计数,递增q的引用计数。 |
p.use_count() | 返回与p共享对象的智能指针数量 |
p.unique() | 若p.use_count()为1,返回true否则false |
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。
shared_ptr<int>p3=make_shared<int>(42); //指向一个值为42的int的shared_ptr
shared_ptr<int>p5=make_shared<int>(); //初始化值为0
shared_ptr<string> p4=make_shared<string>(10,'9'); //p4指向一个值为“9999999999”的string
auto p6=make_shared<vector<string>>(); //正确。
我们可以认为每个shared_ptr都有一个关联的计数器,通常称为引用计数。
递增情况:
无论何时我们拷贝一个shared_ptr,计数器都会递增。例如:用一个shared_ptr去初始化另一个shared_ptr或将它作为参数传递给一个函数或作为函数的返回值,它所关联的计数器都会递增。
递减情况:
当我们给shared_ptr赋予一个新值或shared_ptr被销毁时(例如,一个局部的shared_ptr离开其作用域)时,会递减。
一旦计数器变为0,他就会自动释放自己所管理的对象。
shared_ptr<~>p(q) | p管理内置指针q所指向的对象,q必须是new分配的内存 |
shared_ptr<~>p(u) | p从unique_ptr u那里接管了对象的所有权 |
shared_ptr<~>p(q,d) | p接管了内置指针q所指向对象的所有权,p将使用可调用对象d来代替delete |
p.reset() | 若p是唯一指向的对象,那么就释放p。 |
p.reset(q) | 若传递了可选的参数内置指针q,会令p指向q,否则将p置空 |
p.reset(q,d) | 调用d释放 |
unique_ptr<double> p1;
unique_ptr<int> p2(new int(42));
由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作。但可以传递unique_ptr参数和返回unique_ptr
unique_ptr<double> p1;
unique_ptr<int> p2(new int(42));
unique_ptr<int>p3(p2); //错误
unique_ptr<double>p4;
p4=p1; //错误
unique_ptr<~> sp | 空智能指针,可以指向任意类型的对象 |
p | 将p用作对象,若p指向一个对象,则为true |
*p | 解引用p,获得他指向的对象 |
p->mem | 等价于(*p).mem |
p.get() | 返回p中保存的指针 |
swap(p,q) | 交换p,q的指针 |
p.swap(q) | 等价于swap(p,q) |
unique_ptr |
u2使用D来释放它的指针 |
unique_ptr |
用类型为D的对象d代替delete |
u=nullptr | 释放u指向的对象,将u置空 |
u.release() | u放弃对指针的控制,返回指针并将u置空 |
u.reset() | 释放u指向的对象 |
u.reset(q) | 如果提供了内置指针q,令u指向这个对象,否则将u置空 |
u.reset(nullptr) |
如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不需要时将其释放。
2.4.1、不能使用相同的内置指针初始化多个智能指针。
2.4.2、不delete get()返回的指针。
2.4.3、不使用get()初始化或者reset另一个智能指针。
2.4.4、如果使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就无效了。
2.4.5、如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。
在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针。
//采用默认初始化方式
int *p=new int; //p指向一个动态分配的,未初始化的无名对象。
string *p1=new string; //初始化为空string
//使用直接初始化的方式初始化动态分配的对象
int *pi=new int(1024); //pi指向的对象的值为1024
string *p2=new string(10,'9'); //*p2=“9999999999”
vector<int> *pv=new vector<int>{0,1,2,3,4,5,6,7,8,9};
//值初始化方式
string *ps=new string(); //空字符串
int *p=new int(); // 0
//使用auto
auto p1=new auto(obj); //正确,类型与obj类型相同
auto p2=new auto(a,b,c); //错误,括号中只能有单个初始化器
const int *p=new const int(1024);
const string *p1=new const string;
3.1.2.1、一个动态分配的const对象必须进行初始化。
3.1.2.2、对于一个定义了默认构造函数的类类型,其中const动态对象可以隐式初始化,而其他类型的对象必须显示初始化。
3.1.2.3、由于分配的对象是const的,new返回的指针是一个指向const的指针。
为了防止内存耗尽,在动态内存使用完毕后,必须将其归还给系统。
delete表达式接受一个指针,指向我们要释放的对象。
int i,*pi=&i,*p2=nullptr;
double *pd=new double(33),*pd2=pd;
delete i; //错误,i不是一个指针
delete pi; //未定义,pi指向一个局部变量
delete pd; //正确
delete pd2; //未定义,pd2指向的内存已经被释放了
delete p2; //正确
我们传递给.delete的指针必须指向动态分配的内存,或者是一个空指针。
释放一块并非new分配的内存或者将相同的指针值释放多次,其行为是未定义的。
const int *pc=new const int();
delete pc; //正确
虽然一个const对象的值不能被改变,但他本身是可以被销毁的。
动态对象的生存期,直到被释放才结束,与普通对象不同。
void use_factory(T arg)
{
Foo *p=new factory(arg);
} //p离开了他的作用域,但他所指向的内存没有被释放
当我们delete一个指针后,指针值就变为无效了,虽然指针值无效了,但是很多机器上指针仍然保存着动态内存的地址。在delete之后,指针就变成了人们常说的空悬指针。
3.3.1、忘记delete内存。
3.3.2、使用已经释放掉的对象。
3.3.3、同一块内存释放多次。
int *pia=new int[n]; //默认初始化
int *pia1=new int[n](); //值初始化
int *pia2=new int[3]{0,1,2}; //花括号列表初始化
delete []pia; //释放数组
为了用一个unique_ptr管理动态数组,我们必须在对象类型后面跟一对空方括号。
unique_ptr<int[]>up(new int[10]);
unique_ptr |
u指向一个动态分配的数组 |
unique_ptr |
u指向内置指针p指向的动态分配的数组 |
u[ i ] | 返回u中拥有位置i元素值 |