静态(全局)内存:存储局部static对象、类static数据成员、定义在任何函数之外的对象。static对象在使用之前分配内存,程序结束时销毁。
内存在程序编译的时候已经分配好,运行期间都存在。
栈内存:存储定义在函数内的非static对象(局部非静态变量)。仅在定义的函数块运行时才存在。
栈内存分配运算内置于指令集,效率高,但是容量有限。
堆内存(自由空间):程序运行时分配的对象,声明周期由程序来控制。
动态内存分配。
常量区:存放常量字符串,程序结束时由系统释放。
静态全局变量、全局变量区别
静态局部变量、局部变量区别
直接使用new/delete的类不能使用类对象的拷贝、赋值和销毁的默认形式。
new分配的内存是无名的。
int *p = new int;
默认情况下,动态分配的对象是默认初始化的,也就是内置类型的值是未定义的,类类型的对象将使用默认构造函数构造。
也可以通过在类型名后面加上空括号来使用值初始化。
int *p = new int(); //值初始化,*p=0
可以使用auto来推导类型
string obj("hi");
auto p = new auto(obj); //此时p为string*
auto p = new auto{
a, b, c}; //错误,只能由单个初始化器
定位new
int *p = new int; //如果分配失败,抛出std::bad_alloc异常
int *p2 = new (nothrow) int; //定位new,如果分配失败,返回空指针,bad_alloc和nothrow定义在头文件new中
关于数组
int* p = new int[10](); //10个值初始化的int
delete [] p;
delete之后指针变成了悬空指针,在delete之后置为nullptr。
shared_ptr:允许多个指针同时指向同一个对象。
unique_ptr:只允许一个指针指向某一对象。
weak_ptr:弱引用,指向shared_ptr所管理的对象。
都定义在memory头文件中。
shared_ptr特有操作 | |
---|---|
make_shared (args) | 返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化此对象。 |
shared_ptrp (q) | p是shared_ptr q的拷贝:此操作会递增q中的计数器,q中的指针必须能转换成T* |
p = q | shared_ptr所保存的指针必须能互相转换,此操作会递减p的引用计数,递增q的引用计数,若p的引用计数变为了0,则释放其管理的原来的内存。 |
p.use_count() | 返回与p共享对象的智能指针个数,可能会很慢。 |
p.unique() | 若p.use_count()为1返回true,否则返回false |
shared_ptr<int> p1 = make_shared<int> (42); //创建一个指向442的shared_ptr
shared_ptr<string> p2 = make_shared<string>(10, 'o');
auto p3 = make_shared<vector<string>>();
auto p4(p3); //p3p4指向相同对象
可以使用make_shared标准库函数来创建一个shared_ptr。
shared_ptr<string>sp = make_shared<string>(10, 'c'); //sp指向一个值为cccccccccc的string
//sp2指向一个值初始化的int,即值为0
shared_ptr<int>sp2 = make_shared<int>();
shared_ptr只有在引用计数为0时才会销毁其所指向的对象。
//factory返回一个shared_ptr,指向一个动态分配的对象
shared_ptr<string> factory(){
//一些操作
return make_shared<string>("hi");
}
void use_factory(T arg){
shared_ptr<string> p = factory();
//使用p
}//p离开函数作用域,引用计数变为0,所指向的内存的被释放
shared_ptr<string> use_factory(){
shared_ptr<string> p = make_shared<string>("hi");
//
return p; //返回p时,会递增引用计数
} //p离开了作用域,但是所指向的内存没有被释放。
shared_ptr和new的结合使用
接受指针参数的智能指针的构造函数是explicit的,也就是说不能将一个内置指针隐式转换成一个智能指针。
shared_ptr<int> p1 = new int(1024); //错误,不能使用赋值初始化
shared_ptr<int> p2(new int(1024)); //正确,直接初始化
不要混合使用智能指针和内置指针
void process(shared_ptr<int> ptr){
//参数是传值方式,也就是调用时实参会被拷贝,引用计数会增加
//使用ptr
} //ptr离开作用域,引用计数减一
//正确使用方法,传递一个shared_ptr
shared_ptr<int> p(new int(10));
process(p); //正确,拷贝p会增加它的引用计数,在process内的引用计数至少为2
int i = *p; //正确, 引用计数至少为1
//错误用法
int*x(new int(1024));
process(x); //错误,不能将一个int*转换成shared_ptr
process(shared_ptr<int>(x)); //可以传递,但是函数执行完后x所指向的内存会被释放
int j = *x; //错误,此时x指向的内存已被释放。
不要使用get来初始化智能指针或者为智能指针赋值
get()返回一个指向智能指针管理对象的内置指针。
使用get返回指针的代码不能delete此指针。
shared_ptr<int> sp (new int(10));
int *p = sp.get(); //正确
{
//两个独立的shared_ptr指向相同的内存,这两个shared_ptr的引用计数都是1,该程序块内的shared_ptr失效后会销毁其所指向的对象。
shared_ptr<int> q(p);
}
int foo = *sp; //错误,此时sp所指向的内存已被释放
reset()操作
shared_ptr<int> sp = make_shared<int>(10);
sp.reset(); //此时sp的引用计数为0,会释放其所指向的对象
sp.reset(new int(1024)); //sp指向一个新的值为1024的int对象
使用智能指针,即使程序出现异常提前结束,智能指针也能保证正确地释放指针指向的内存。而内置指针无法实现这一点。
注意陷阱
- 不要使用相同的内置指针值初始化(或者reset)多个智能指针。因为这样其中某一个智能指针失效后会销毁所指向的对象,从而造成另一个智能指针指向的对象不存在了。
- 不要delete get()返回的指针。
- 不要用get()初始化或者reset另一智能指针。
- 如果智能指针管理的资源不是new分配的内存,需要传递一个删除器。
- 记住,使用get()返回的指针,在最后一个对应的智能指针被销毁后,返回的指针就无效了。
某个时刻只能有一个unique_ptr指向一个给定对象,不支持普通的拷贝和赋值操作。
初始化unique_ptr必须使用直接初始化形式。
unique_ptr<int> up (new int(10));
unique_ptr<int> up2 (up); //错误。不支持拷贝
unique_ptr<int> up3 = up; //错误,不支持赋值
unique_ptr操作 | |
---|---|
unique_ptr |
空的unique_ptr,会使用类型为D的可调用对象来释放它所指向空间。D默认为delete操作符 |
unique_ptr |
空unique_ptr,调用类型为D的对象d来代替delete |
u = nullptr; | 释放u指向的对象,将u置空 |
u.release(); | u放弃对指针的控制权,返回所指向的指针,并置空u |
u.reset(); | 释放u所指向的对象 |
u.reset(q); | 令u指向内置指针q,否则置空u |
release()返回unique_ptr当前保存的指针并将其置为空,常用来初始化或给另一智能指针赋值。
unique_ptr<string> p1 (new string("hi"));
unique_ptr<string> p2 (new string("jack"));
//将p1的所有权转移给p2
p2.reset(p1.release());
唯一一个可以拷贝unique_ptr的例子就是从函数返回一个unique_ptr。
unique_ptr<int> clone(p){
return unique_ptr<int>(new int(p));
}
//这样也行
unique_ptr<int> clone(p){
unique_ptr<int> ret (new int(p));
return ret;
}
auto_ptr是一个早期版本的智能指针,具备部分unique_ptr功能,但已被废弃,尽量不要使用。
weak_ptr是一种不控制所指向对象生命周期的智能指针,指向一个由shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr上不会改变shared_ptr的引用计数。
不能直接使用weak_ptr访问所指向的对象,而需要使用lock()成员函数。
auto p = make_shared<int>(42);
weak_ptr<int> wkp (p); //直接初始化wkp
auto mp = wkp.lock(); //mp指向wkp所指向的对象。
关于动态数组,使用new T[]分配返回的并不是一个数组,而是一个指向数组元素类型的指针。动态数组并不是数组类型。
auto dp = new int[10]; //10个int的数组,返回指向首元素的指针
auto ds = new string[10]; //同理
auto de = new int[0]; //动态分配一个空的数组是合法的,但是不能解引用
delete [] dp;
delete [] ds; //释放动态数组空间,数组元素按逆序销毁。
delete [] de;
标准库提供了一个可以管理new分配的动态数组的unique_ptr。
unique_ptr<int[]> up (new int[10]);
up.release(); //自动调用delete[]销毁指针
指向数组的unique_ptr的操作 | |
---|---|
指向数组的unique_ptr不支持成员访问运算符(.和->) | |
unique_ptr |
u可以指向一个动态分配的数组 |
unique_ptr |
u指向内置指针所指向的动态数组 |
u[i] | 返回u拥有的数组中的第i个元素。 |
shared_ptr默认不支持管理动态数组,如果想使用shared_ptr来管理动态数组,必须提供自己的删除器。
shared_ptr<int> sp (new int[10], [](int *p) {
delete [] p;});
sp.reset(); //调用lambda表达式delete[]数组
使用allocator类将内存的分配和对象的构造分离开来。
可以在分配的内存上按需构造对象,提高效率。
如果不使用allocator,没有默认构造函数的类就无法使用动态分配数组了。
allocator类及其算法 | |
---|---|
allocator a; | 定义allocator对象a,可以为类型T的对象分配内存 |
a.allocator(n); | 分配一个保存n个类型为T的对象的未构造内存 |
a.construct(p, args); | p必须是指向类型为T*的未构造的内存,args被传递给类型为T的构造函数,用于在内存中构造对象。 |
a.destroy§; | 对p指向的对象执行析构函数。 |
a.deallocate(p, n); | 释放从T* p所指向的位置开始的n个类型为T的对象,p必须是由allocator返回的指针,n必须是创建时的大小。在deallocate之前,内存中的对象必须先被destroy。 |
allocator分配的内存是原始的,未构造的。
allocator<string> alloc;
auto p = alloc.allocate(10); //分配10个未初始化的string
auto q = p; //q指向最后构造的元素之后的位置
alloc.construct(q++); //q为空字符串
alloc.construct(q++, 10, 'c'); //*q为cccccccccc
cout << *p << endl; //正确,因为p所指向的内存已有对象
cout << *q << endl; // 错误,q指向未构造的内存,因为q++指向下一个未构造的内存了。
while(q != p){
alloc.destory(--q); //销毁q指向的对象
}
//之后可以将分配的内存释放掉
alloc.deallocate(p, 10); //之所以是10是因为deallocate的大小必须和allocate的大小一致
如果觉得向上面一个一个construct比较麻烦,可以使用标准库的拷贝填充算法。
allocator拷贝填充算法 | |
---|---|
uninitialized_copy(b, e, b2); | 从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中,b2指向的内存必须足够大。 |
uninitialized_copy_n(b, n, b2); | 从迭代器b开始,拷贝n个元素到迭代器b2指向的原始内存中。 |
uninitialized_fill(b, e, t); | 在迭代器b,e指定的范围内创建对象,对象的值均为t的拷贝。 |
uninitialized_fill_n(b, n, t); | 从迭代器b指向的内存开始创建n个对象。其值为t |
//p指向一个分配大小为vi两倍的内存
auto p = alloc.allocate(vi.size() * 2);
//将vi中的元素拷贝到p指向的内存中, 返回的q指向最后一个构造的对象的后一个位置
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
uninitialized_fill_n(q, vi,size(), 42); //将剩余的内存全部初始化为42