内存类型分类
1 静态内存
静态内存用来保存局部static
对象,类static
数据成员,以及定义在任何函数之外的变量。
2 栈内存
栈内存用来保存定义在函数内的非static
对象。分配在静态内存和栈内存的变量由编译器自动创建和销毁。
3 堆内存(自由空间)
用来存储动态内存,动态对象的创建和销毁都由代码显式操作。
智能指针
C++中内存的管理是通过new
和delete
完成的,由于内存的释放容易遗漏,所以内存泄漏时常发生。
为了更容易且更安全地使用动态内存,新标准在memory
头文件中定义了shared_ptr
、unique_ptr
和weak_ptr
三种智能指针来管理动态内存。
1 shared_ptr
1.1 使用说明
智能指针也是模板,需要提供所指向的对象,默认初始化的智能指针中保存着一个空指针。
shared_ptr<string> p1; //可以指向string类型
shared_ptr<list<int>> p2; //可以指向list<int>类型
if(p1 && p1->empty()) //如果p1为空,则将“hi”赋予string
*p1 = "hi";
1.2 shared_ptr和unique_ptr都支持的操作
p.get();//返回p中保存的指针,当智能指针释放了其对象,返回的指针所指向的对象也被销毁了
swap(p,q); //交换p和q中指针
q.swap(p); //同上
局部shared_ptr
离开其作用域,引用计数会递减,当引用计数为0时,就会自动释放它所管理的内存。
1.3 仅shared_ptr支持的操作
make_shared<T>(args); //返回一个shared_ptr指向一个T对象,并用args初始化对象
shared_ptr<T> p(q); //p是q的拷贝,此操作递增q中计数器
p = q; //递减p中计数器,递增q中计数器
p.unique(); //若p.use_count()为1,返回true,否则false
p.use_count(); //返回指针的引用计数,一旦计数为0,指针会释放自己管理的对象。
1.4 使用动态内存的三个原因
- 程序不知道自己需要使用多少对象
- 程序不知道所需对象的准确类型
- 程序需要在多个对象之间共享数据
以下是由于第三个原因使用动态内存的例子
template<typename T>
class Test{
public:
Test():data(make_shared<vector<T>>()){}
Test(std::initiallizer_list<T> li):data(make_shared<vector<T>>(li)){}//列表初始化
void pop_back() {data->pop_back();}
T& top() const {return data->back();}
bool empty() const {return data->empty();}
size_t size() const {return data->size();}
void push_back(const T& d){data->push_back(d);}
private:
shared_ptr<vector<T>> data;
};
1.5 new和delete动态用法
string *s = new string;
string *s = new string("2333");
auto p = new auto(obj);//括号中必须只有单一初始化器才能这么用
auto p = new auto({a,b});//错误用法
//正常情况下,new失败会抛出std::bad_alloc异常
int * p = new (nothrow) int;//若分配失败,返回空指针,称为定位new
delete p;
delete []p;
shared_ptr<int> p(new int(10));//正确用法
shared_ptr<int> p = new int(10);//错误用法,必须使用直接初始化
shared_ptr<int> f(){
return new int;//不允许隐式转换,错误用法
}
shared_ptr<int> f(){
returb shared_ptr<int>(new int);//智能指针式explicit的,正确用法
}
1.6 定义和改变shared_ptr
1)shared_ptr<T> p(q);
p管理内置指针q所指向的对象,q必须指向new分配的内存,且能转换成T*类型
2)shared_ptr<T> p(u);
p从unique_ptr u哪里接管对象的所有权,并将u置为空
3)shared_ptr<T> p(q,d);
p接管内置指针q所指向对象的所有权,p将使用可调用对象d来代替delete
4)shared_ptr<T> p(p2,d);
p是shared_ptr p2的拷贝,并p调用可调用对象d来代替delete
5)p.reset();
如果p是唯一指向对象的shared_ptr,reset会释放此对象
p.reset(q);
若传递了可选的参数内置指针q,会令p指向q,否则将p置空
p.reset(q,d);
若还传递了可选参数d,将会调用d而不是delete释放q
//注意:
//1. 虽然C++提供了内置指针和智能指针的转换,但是不要混用两种指针
//2. 不要使用get初始化另一个智能指针或为智能指针赋值
//3. 当将shared_ptr绑定到一个普通指针时,内存管理的责任已经交给了此shared_ptr,
//不能再使用内置指针来访问原来指向的内存了
//4. shared_ptr不支持下标运算法,只能使用点和箭头运算符
2 unique_ptr
与shared_ptr
不同的是,某个时刻只能有一个unique_ptr
指向一个给定对象,当unique_ptr
被销毁时,它所指向的对象也被销毁。定义一个unique_ptr
时,必须将它绑定到一个new
返回的指针上。
unique_ptr<double> p1;
unique_ptr<int> p2(new int(43));
因为一个unique
拥有一个对象,所以它不支持普通的拷贝或赋值操作,除上文所提到的操作外,unique_ptr
还支持:
1)unique_ptr<T,D> u2;
定义一个空的u2,u2会使用一个类型为D的可调用对象来释放它的指针
2)unique_ptr<T,D> u(d);
定义一个空的u,指向类型为T的对象,用类型为D的对象d代替delete
3)u = nullptr;
释放u指向的对象,将u设为空
4)u.release();
u放弃对指针的所有权,返回指针,并将u置为空
5)u.reset();
释放u指向的对象
u.reset(q);
如果提供了内置指针q,则令u指向这个对象,否则将u置空
u.reset(nullptr);
//注意:unique_ptr只能使用下标运算符,不能使用点和箭头运算符
3 weak_ptr
shared_ptr
类似于操作系统中的硬链接,weak_ptr
则与软链接相似。weak_ptr
是一种不控制所指向对象生存周期的智能指针,它指向一个由shared_ptr
管理的对象,将一个weak_ptr
绑定到shared_ptr
不会改变shared_ptr
的引用计数。
weak_ptr<T> w;
weak_ptr<T> w(sp);
w = p;//p 为weak ptr或者shared ptr
w.reset();//将w置为空
w.use_count();
w.expired();//若w.use_count()为0,则为true,否则false
w.lock();//若expired返回true,则返回一个空shared_ptr,否则指向w的shared_ptr