【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>
前言
1.生命周期
全局对象:程序启动时分配,程序结束时销毁
局部自动对象:执行流进入其定义的块时分配,执行流退出其定义的块时销毁
局部static对象:程序启动时分配(但在其定义的块或作用域内起作用),程序结束时销毁
动态分配的对象:生命周期与创建地点无关,只有当显示的被释放时,才会被销毁
智能指针:标准库定义,用于管理动态分配的内存,当一个对象应该被释放时,指向它的智能指针可以确保自动的释放它
2.内存分类
静态内存:用于存储局部static对象、类的static数据成员、全局变量
栈内存:用于存储局部非static对象
堆内存(内存池):用于存储动态分配的对象——动态分配的对象,其生命周期由程序控制,例如使用new或delete
3.动态内存
动态内存使用过程中容易产生的问题
内存泄露:使用后忘记释放内存
引用非法内存:在尚有指针引用内存的情况下就释放它
使用动态内存的原因:
程序不知道自己需要使用多少对象
程序不知道所需对象的准确类型
程序需要在多个对象间共享底层数据——若两个对象共享底层数据,当某个对象销毁时,我们不能单方面的销毁底层数据
4.智能指针
智能指针和常规指针直接的区别:智能指针能够自动释放所指向的内存(类似java中的垃圾回收机制),而常规指针不能。
智能指针默认初始化为nullptr
智能指针分类
shared_ptr:允许多个指针指向同一对象
unique_ptr:独占所指向的对象
5.智能指针和异常
如果使用智能指针,即使程序非正常结束(异常),只要程序离开作用域,局部变量(智能指针)就会被销毁,若此时引用计数为0,则所指对象或内存也会被销毁。
如果使用普通指针,则当程序非正常结束(异常)时,由于未进行delete操作,因此指针所指的对象或内存未被释放,造成内存泄露。
6.使用智能指针的基本规范(智能指针的陷阱)
不使用相同的内置指针值初始化(或reset)多个智能指针,否则容易多次释放同一对象或内存——因为这样产生的智能指针是相互独立的
int *p = new int(1);
shared_ptr<int> p1(p);
shared_ptr<int> p2(p); // 此时p1和p2相互独立且引用计数都为1,离开作用域时会释放同一对象两次
不delete``get()返回的指针,否则离开作用域时又要释放一次内存
{
shared_ptr<int> p = make_shared<int>(1);
delete p.get(); // 释放一次内存
} // 又要释放一次内存
不使用get()初始化或reset另一个智能指针,否则这两个智能指针是独立的,离开作用域时会释放同一内存两次
{
shared_ptr<int> p1 = make_shared<int>(1);
shared_ptr<int> p2(p1.get()); // 此时p1和p2相互独立且引用计数都为1
} // 释放同一内存两次
如果你使用get()返回的指针,记住当最后一个对于的智能指针销毁后,你的指针就无效了——因为此时该指针为悬空指针
int *p = nullptr;
{
shared_ptr<int> p1 = make_shared<int>(1);
p = p1.get();
} // 此时释放p1所指向的内存,且p为悬空指针
如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器(用于代替默认的delete)——因为智能指针的自动释放默认使用delete,而delete只能用于动态内存或nullptr
智能指针
智能指针类所支持的操作
shared_ptr和unique_ptr都支持的操作
操作功能
shared_ptr<T> sp、unique_ptr<T> up空智能指针,可以指向类型为T的对象
*p解引用p所指的对象
p->data访问对象*p中的data元素
p.get()返回p中保存的指针(值)
swap(p, q)、p.swap(q)交换p和q中保存的指针(值)
shared_ptr独有的操作
操作功能
make_shared<T>(args)返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化此对象
shared_ptr<T> p(q)、p = q、shared_ptr<T> p(q, d)拷贝初始化,p和q指向同一对象,其中d是一个删除器(用来代替默认的delete)
p.unique()若p.use_count() == 1,则返回true,否则返回false
p.use_count()返回与p共享对象的智能指针数量(引用计数)
p.reset()、p.reset(q)、p.reset(q, d)若p是唯一指向其对象的shared_ptr,则reset会释放此对象。若传递了可选的参数(内置指针)q,则会令p指向q,否则会将p置为空。若还传递了参数(可调用对象)d,则会调用d而不是delete来释放p
unique_ptr独有的操作
操作功能
unique_ptr<T> u指向类型T的对象的空unique_ptr,且默认使用delete释放指针
unique_ptr<T, D> u指向类型T的对象的空unique_ptr,且使用类型为D的可调用对象来释放指针
unique_ptr<T, D> u(d)指向类型T的对象的空unique_ptr,且使用类型为D的可调用对象d代替delete来释放指针
u = nullptr释放u指向的内存,并将u置为空
u.release()先使指针对象u放弃对指针(值)的控制权,再返回指针(值),最后将u置为空——即断开指针对象和所指对象之间的联系(只断开联系,不释放内存)
u1.reset()、u2.reset(q)、u3.reset(nullptr)重置指针(值),使其指向新对象或为空
shared_ptr类
关键概念
初始化
用make_shared初始化(最安全)
拷贝初始化(拷贝另一个智能指针)或直接初始化(可由任意类型指针为参数)
引用计数
引用计数决定何时释放内存
智能指针独有,内置指针没有
相互关联的智能指针的引用计数相同
内存释放
析构函数
默认delete
自定义删除器
初始化
使用make_shared标准库函数初始化(最安全,且会为对象分配内存):
shared_ptr<string> p1 = make_shared<string>("chensu");
shared_ptr<int> p2 = make_shared<int>(24);
shared_ptr<vector<string>> p3 = make_shared<vector<string>>(); // 当参数为空时,默认进行值初始化,此时p3所指对象为空vector<string>
class Person {
private:
string name;
int age;
public:
Person(const string &_name, int _age) : name(_name), age(_age) {}
void print() const { cout << name << ", " << age << endl; }
};
shared_ptr<Person> p4 = make_shared<Person>(Person("chensu", 24)); // 参数必须与Person类的某个构造函数相匹配
使用make_shared为智能指针分配动态内存比用new直接初始化更安全的原因:使得在分配对象的同时就将shared_ptr与之绑定,避免将同一块内存绑定到多个独立创建的shared_ptr上:
Person *p = new Person("chensu", 24);
shared_ptr<Person> p1(p);
shared_ptr<Person> p2(p); // 此时p1和p2相互独立且引用计数都为1,程序结束时会释放统一内存两次
拷贝初始化(只拷贝指针,无需重新为对象分配内存)
拷贝智能指针
auto p = make_shared<int>(42); // 为p指向的对象分配内存,且此时该对象只有p一个引用者
auto q(p); // p和q指向同一个对象,此时该对象有两个引用者
拷贝普通指针(和new结合):由于接受指针参数的智能指针构造函数是explicit的,因此我们不能将一个普通指针隐式转换为智能指针,而必须使用直接初始化形式或将普通指针强制类型转换为智能指针。
shared_ptr<int> p = new int(42); // 错误,不支持隐式转换,可以使用直接初始化或强制类型转换
shared_ptr<int> p(new int(42)); // 正确,支持直接初始化形式
shared_ptr<int> p = shared_ptr<int>(new int(42)); // 正确,能够拷贝强制转换后的智能指针
初始化并自定义释放操作:默认情况下,系统通过delete运算符来自动释放智能指针,因此一个用来初始化智能指针的普通指针必须指向动态内存,否则结果未定义。但我们可以自定义释放操作来代替默认的delete,这样就可以用指向非动态内存的普通指针来初始化智能指针
shared_ptr<int> p(q, d); // p将使用可调用对象d来代替delete
引用计数
每个shared_ptr都各自关联一个计数器,以免发生引用非法内存(即在尚有指针引用内存的情况下就释放该内存),引用计数的初始值为1
智能指针的引用计数=所指对象绑定的互相关联的智能指针的数量
智能指针必须互相关联的原因:拷贝智能指针有两个阶段,且这两个阶段只有互相关联的智能指针才能完成,互相独立的智能指针没有这两个阶段
拷贝引用计数(将源智能指针的引用计数拷贝给目的智能指针)
共同递增引用计数(分别将源智能指针和目的智能指针的引用计数加1)
例子:
shared_ptr<int> p(new int(42)); // p的引用计数为1
shared_ptr<int> q(p); // 因为p和q相互关联,所以它们的引用计数都递增为2
{
shared_ptr<int> r(p.get()); // 因为p和r相互独立(通过get初始化的指针互相独立),所以它们的引用计数都不变(既不拷贝,也不共同递增),其中p为2,r为1
} // 程序块结束,r递减为0,并释放r所指向的内存,此时p和q都为悬空指针
int a = *p; // 未定义,因为此时p为悬空指针
永远不要用get获得的指针值初始化一个智能指针,因为这样做的话,源指针和目的智能指针是相互独立的
指向同一对象的互相关联的智能指针具有相同的引用计数
智能指针p所指对象的引用计数递增的情况——即p所指的对象与和p互相关联的其它智能指针发生新的绑定关系:
用p初始化另一个智能指针q(对象绑定另一个智能指针):
auto p = make_shared<int>(42); // 此时p的引用计数为1
auto q = p; // 此时p和q的引用计数为2
将p作为参数传递给一个函数(对象绑定形参):
auto p = make_shared<int>(42); // 此时p的引用计数为1
function(p); // 此时p的引用计数为2
将p作为函数的返回值(对象绑定一个右值指针或左值指针):
shared_ptr<int> function() {
auto p = make_shared<int>(42); // 此时p的引用计数为1
return p; // 此时p的引用计数为2
}
... // 即使离开了function函数,p所指的对象的引用计数依旧为1
智能指针p所指对象的引用计数递减的情况——即p所指的对象与和p互相关联的其它智能指针解除旧的绑定关系:
为p赋予一个和原来不同的新值:
auto p = make_shared<int>(42);
auto p = nullptr; // 此时p的引用计数为0
p被销毁(例如程序离开p的作用域):
void function() {
auto p = make_shared<int>(42); // 此时`p`的引用计数为1
}
... // 此时`p`的引用计数为0
一旦一个shared_ptr的引用计数变为0,它就会使用析构函数自动释放自己所管理的对象(类似于java中的垃圾回收机制)
内存释放
析构函数: 智能指针通过自己的析构函数完成销毁操作,析构函数会递减它所指向对象的引用计数,若引用计数变为0(即该对象此时没有绑定任何指针),则析构函数会销毁对象,并释放对象所占用的内存
注意事项
由于在最后一个shared_ptr销毁之前内存都不会释放,因此保证shared_ptr在无用之后不再保留就非常重要了,若你忘记销毁程序中不再需要的shared_ptr,虽不会操作内存泄露,但会浪费内存,造成程序运行较卡
销毁不再需要的shared_ptr指针的方法是令其为nullptr
默认情况下,系统通过delete运算符来自动释放智能指针,因此一个用来初始化智能指针的普通指针必须指向动态内存,否则结果未定义。但我们可以自定义释放操作来代替默认的delete,这样就可以用指向非动态内存的普通指针来初始化智能指针
警告
不要混合使用普通指针和智能指针——因为混合使用会干扰内存的正常释放
void process(shared_ptr<int> ptr) { ... } // 指针进入函数时引用计数加1,离开时引用计数减1
/* 情况1:只使用智能指针 */
shared_ptr<int> p(new int(42)); // p所指内存的引用计数为1
process(p); // p离开后引用计数不变,仍为1
int i = *p; // 正确,因为内存未被释放
/* 情况2:混合使用智能指针和普通指针 */
int *q(new int(42)); // q所指内存的引用计数为0
process(shared_ptr<int>(q)); // q离开后引用计数不变,仍未0,但在离开时由于形参的类型为shared_ptr,因此系统会自动释放q所指向的内存,此时q为悬空指针
int j = *q; // 未定义,此时q为悬空指针
unique_ptr类
关键概念
初始化
拷贝初始化(拷贝另一个智能指针)或直接初始化(可由任意类型指针为参数)
对指针(值)的控制权
所有操作都必须保持指针值的唯一性
某一时刻只能有一个unique_ptr指向一个给定的对象——保持唯一性
不能拷贝或赋值unique_ptr(例外:可以拷贝或赋值一个将要被销毁的unique_ptr)——保持唯一性:
unique_ptr<int> clone(int p) {
return unique_ptr<int>(new int(p));
}
unique_ptr<int> q = clone(1); // 正确,该函数的返回值是一个右值(临时变量)
允许转移对指针(值)的控制权,但源指针值必须改变——保持唯一性
内存释放
当unique_ptr为nullptr时,其原来所指向的内存会被释放
对象与unique_ptr指针之间的联系——若联系断开,则即使unique_ptr指针被销毁,对象也不会被释放
只有与对象相关联的unique_ptr被销毁时,对象才会被自动释放
对unique_ptr指针(值)的控制权
使用release()释放对指针值或对象内存的控制权(但不会释放所指对象的内存)
{
unique_ptr<int> u(new int(1));
u.release(); // 释放控制权(断开u和对象之间的联系)
} // 离开作用域,销毁u,但由于此时u和对象之间无关联,因此无法释放对象内存,故造成内存泄漏
使用release()和reset()转移控制权(目的unique_ptr原来所指向的对象会被自动释放)
{
unique_ptr<int> u1(new int(1));
unique_ptr<int> u2(new int(2));
u2.reset(u1.release()); // u1先释放对指针值的控制权(断开与对象的联系),再返回指针值给u2,接着将u1置为空,此时u2掌握该指针值(或对象内存)的控制权,最后自动释放之前u2控制的内存
} // 离开作用域,释放此时u2控制的内存
向unique_ptr传递删除器
默认情况下,使用delete释放它指向的对象
由于我们必须在尖括号中unique_ptr指向的对象类型之后提供删除器类型,因此重载一个删除器会影响到unique_ptr的类型以及如何构造(或reset)该类型的对象:
void my_deleter(int *ptr) { delete ptr; }
int *q = new int(1);
unique_ptr<int, decltype(my_deleter) *)> p(q, my_deleter)
weak_ptr类
将一个weak_ptr绑定到shared_ptr上不会改变shared_ptr的引用计数
一旦weak_ptr绑定的最后一个shared_ptr被释放,该weak_ptr就成为悬空指针
由于对象可能已被释放(weak_ptr成为了悬空指针),因此我们不能使用weak_ptr直接访问对象,而必须调用lock检查weak_ptr所指的对象是否存在,若存在则返回一个shared_ptr:
shared_ptr<int> p = make_shared<int>(42);
weak_ptr<int> wp(p);
shared_ptr<int> q = wp.lock();
总结
智能指针要特别注意同一内存多次释放和内存为释放问题
shared_ptr的引用计数决定是否释放其所指向的内存
始终保证至少有一个unique_ptr和对象之间的联系,否则容易造成内存泄漏
来源:oschina
链接:https://my.oschina.net/u/2445348/blog/652764