静态内存: 保存局部 static 对象、类 static 数据成员以及定义在任何函数之外的变量。
栈内存:保存定义在函数内的非 static 对象
自由空间或堆:存储动态分配的对象,即程序运行时分配的对象。
分配在静态或栈内存中的对象有编译器自动创建和销毁,而动态对象的生存期由程序来控制,当动态对象不再使用时,代码必须显式地销毁它们。
头文件 #include
智能指针默认初始化时保存一个空指针(nullptr),用法与普通指针类似,解引用一个智能指针返回它所指向的对象。
shared_ptrp1
shared_ptr>p2
//如果p1不为空,检查它是否指向一个空的string
if(p1 && p1->empty())//p1指针为空,if判断为false,不会进入
{
//如果p1指向空的string,解引用p1,将一个新值赋予string
*p1 = "hello,world!";
}
表1:shared_ptr 和 unique_ptr都支持的操作
操作 | 含义 |
---|---|
shared_ptrsp | 空智能指针,可以指向类型为T的对象 |
unique_ptrup | |
p | 将p用作一个条件判断,若p指向一个对象,则为true |
*p | 解引用p,获得它指向的对象 |
p->mem | 与(*p).mem等价 |
p.get() | 返回p中保存的指针。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了 |
swap(p,q)与p.swap(q) | 交换p与q中的指针 |
表2:shared_ptr 独有的操作
操作 | 含义 |
---|---|
make_shared(args) | 返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化此对象。 |
shared_ptrp(q) | p是shared_ptr q 的拷贝;此操作会递增q中的计数器,q中的指针必须能转换为T* |
p=q | p和q都是shared_ptr,所保存的指针必须能够相互转换,此操作会递减p的引用计数,递增q的引用计数;若p的计数为零,则将其管理的原内存释放 |
p.unique() | 若p.use_count()为1,返回true;否则返回false |
p.use_count() | 返回与p共享对象的智能指针数量;可能很慢,主要用于调试 |
调用make_shared分配和使用动态内存是最安全的,此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。代码如下:
//指向一个值为42的int的shared_ptr
shared_ptrp3 = make_shared(42);
//p4指向一个值为“9999999999”的字符串
shared_ptrp4 = make_shared(10,9);
//p5指向一个值初始化的int,即值为0
shared_ptrp5 = make_shared();
//也可以用auto保存make_shared结果
auto p6 = make_shared>();
shared_ptr的自动释放通过引用计数来实现,当引用计数为0时,智能指针自动释放。
auto p = make_shared(42);//p指向的对象只有p一个对象
auto q(p);//p和q指向相同的对象,此时该对象有p和q两个引用者
//注意初始化和等号(=)的区别
auto r = make_shared(42);//r指向的int只有一个引用者
r = q;//给r赋值,另其指向另一个地址
//递增q指向的对象的引用计数
//递减r原来的指向的对象的引用计数
//r原来指向的对象已没有引用者,自动释放
看代码:
//factory 返回一个shared_ptr,指向一个动态分配的对象
shared_ptr factory(T arg)
{
//恰当的处理arg
//shared_ptr负责释放内存
retrun make_ptr(arg);
}
factory返回一个shared_ptr,我们可以确保它分配的对象会在恰当的时候被释放。如:
void use_factory(T arg)
{
shared_ptr p = factory(arg);
//使用p;
}//p离开作用域,其指向的内存被自动释放
如果有其他shared_ptr也指向这块内存,则其指向的内存不会被释放。如:
void use_factory(T arg)
{
shared_ptr p = factory(arg);
//使用p;
return p;//当返回p时,引用计数会递增
}//p离开作用域,其指向的内存不会被释放
我们应时刻保证无用的 shared_ptr 不会保留,如果忘记销毁无用的 shared_ptr,程序仍然可以正确执行,但会浪费内存。
注意:某种情况下,将 shared_ptr 保存至容器中,重排了容器后,不再需要某些元素,这时应该用erase删除那些不再需要的 shared_ptr 元素。
到目前为止,使用过的类中分配的资源都与对应对象生存期一致。如:拷贝数据时,原数据和数据副本是相互分离的,即在内存中分别占了一块内存。
vector v1; //空vector
{
//新作用域
vector v2 = {"a","an","the"};
v1 = v2;//从v2拷贝元素至v1
}//v2离开作用域即被销毁,v1中的元素为原v2中元素的拷贝
但某些类分配的资源具有与原对象相独立的生存期。也就是,多个对象共享底层的数据,某个对象被销毁时,不能单方面的销毁底层数据:
//我们实现的 Blob 类应该如下
Blob b1;// 空 Blob
{
//新作用域
Blob b2 = {"a","an","the"};
b1 = b2; // b1、b2 共享底层数据
}// b2 被销毁但 b2 中的元素不能销毁
为实现共享内存,在类中使用 shared_ptr 来管理动态分配的 vector。shared_ptr 的成员会记录有多少个类共享 vector,在最后一个使用者被销毁时自动释放 vector。
class StrBlob
{
public:
typedef std::vector::size_type size_type;
StrBlob();
StrBlob(std::initializer_list i1);
size_type size() const {return data->size();}
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>data;
//如果 data[i] 不合法,抛出异常
void check(size_type i,const std::string &msg)const;
};
两个构造函数,其中第二个构造函数接受一个 initializer_list 的参数,此构造函数将拷贝列表中的值来初始化 vector 的元素。
StrBlob::StrBlob():data(make_shared>){}
StrBlob::StrBlob():data(initializer_listi1):data(make_shared>(i1)){}
Note: initializer_list 形参
如果参数的实参数量未知但是全部实参的类型相同,则可以使用 initializer_list 类型的形参。initializer_list 是一种标准库类型,用于表示某种特定类型的值数组。头文件 #include
表3 initializer_list 提供的操作
操作 | 含义 |
---|---|
initializer_listlst; | 默认初始化;T类型元素的空列表 |
initializer_listlst{a,b,c…}; | lst的元素数量和初始值一样多;lst的元素是对应初始值的副本;列表中的元素是const |
lst2(lst);或lst2=lst | 拷贝列表,但不会拷贝原列表中的元素,即拷贝后数据在内存中是共享的 |
lst.size() | 列表中的元素数量 |
lst.begin() | 返回指向lst中首元素的指针 |
lst.end() | 返回指向lst中尾元素下一位置的指针 |
与vector不一样的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中的元素值。
使用如下:
void error_msg(initializer_listi1)
{
for(auto beg = i1.begin();beg != i1.end();++beg)
{
cout<< *beg << " ";
}
cout<
void StrBlob::check(size_type i,const string &msg)const
{
if(i>=data->size())
{
throw out_of_range(msg);
}
}
string& StrBlob::front()
{
//如果vector为空,check会抛出一个异常
check(0,"front on empty strBlob");
rturn data->front();
}
string& StrBlob::back()
{
check(0,"back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back()
{
check(0,"pop_back on empty StrBlob");
data->pop_back();
}
C++定义了两个运算符分配和释放动态内存。运算符new分配内存,delete释放由new分配的内存。
int *pi = new int; // pi指向动态分配,未初始化的无名对象
int *pi = new int();//值初始化为0
string *ps = new string; //初始化为空string
int *pi = new int(1024); //直接初始化
string *ps = new string(3,9); // *ps值为999
//vector有十个元素,值依次从0-9
vector *pv = new vector{0,1,2,3,4,5,6,7,8,9};
//p1指向一个与obj类型相同的对象,并用obj进行初始化
auto p1 = new auto(obj);
//也可以动态分配const对象
const int *pci = new const int(1024);
const string *pcs = new const string;
int *p(new int(42));
auto q = p; //p和q指向同一片内存
delete p;//p和q指向的内存释放掉了
p = nullptr;//只对p有效
shared_ptr p1 = new int(1024);//错误,不能隐式转化
shared_ptr p2(new int(1024)); //正确:直接初始化
shared_ptr clone(int p)
{
return new int(p);////错误,不能隐式转化
}
shared_ptr clone(int p)
{
return shared_ptr(new int(p));//正确:显示用int*创建shared_ptr
}
void process(shared_ptrptr)
{
//使用ptr
}//ptr离开作用域,被销毁
int *x(new int(1024));//
process(x);//错误,不能隐式转换
//合法,但参数是临时的智能指针,函数调用结束会释放其指向的内存,导致x也被释放,成为空悬指针
process(shared_ptr(x));
int j = *x;//未定义,x指向的内存已被智能指针释放
p = new int(1024);//错误,不能隐式转换
p.reset(new int(1024)); // 正确:p指向一个新对象
if(!p.unique())
{
p.reset(new string(*p));//我们不是唯一用户;分配新的拷贝
}
*p += newVal;//现在知道自己是唯一用户,可以改变对象的值