静态内存:保存局部static对象、类static数据成员以及定义在任何函数之外的变量。
栈内存: 保存定义在函数内的非static对象
动态内存(堆): 存储动态分配的对象
为了更安全的使用动态内存,,新的标准库提供了两种智能指针(smart pointer,定义在memory头文件中),与常规指针的区别在于负责自动释放所指向的对象。两种智能指针的区别在于管理底层指针的方式:
#include
shared_ptr unique_ptr |
空智能指针,可以指向类型为T的对象 |
p | 将p作为一个条件判断,若p指向一个对象,则为true |
*p | 解引用p,或得它指向的对象 |
p->mem | 等价(*p).mem |
p.get() | 返回p中保存的指针(内置指针,且不能delete此指针)。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了。 |
swap( p, q) p.swap(q) |
交互p和q中的指针 |
make_shared |
返回一个shared_ptr,指向一个动态分配的类型为T的对象,使args初始化对象 |
shared_ptr |
p是shared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换成*T |
p = q | p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放 |
p.unique() | 若p.use_count()为1,返回true。否则返回false |
p.use_count() | 返回与p共享对象的只能指针数量:可能很慢。主要用于调试 |
每个shared_ptr都有一个关联的计数器,通常称为引用计数(reference count)
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(); };
//add element
void push_back( const std::string &t) { data.push_back(t)};
void pop_back();
//visit element
std::string& front();
const std::string& front() const;
std::string& back();
const std::string& back() const;
private:
std::shared_ptr> data;
//if data[i] illegal ,return out_of_range
void check( size_type i, const std::string &msg) const;
};
StrBlob::StrBlob(): data(make_shared>()) {}
StrBlob::StrBlob( std::initializer_list i1):
data(make_shared>(i1)) { }
shared_ptr |
p管理内置指针q所指向的对象;q必须指向new分配的内存,且能转换为T*类型 |
shared_ptr |
p从unique_ptr u那里接管了对象的所有权;将u置位空 |
shared_ptr |
p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用对象d来代替delete |
shared_ptr |
p是shared_ptr p2的拷贝。唯一区别是p将用可调用对象d来代替delete |
p.reset() p.reset( q) p.reset( q, d) |
若p是唯一指向其对象的shared_ptr,reset会释放对象 若传递了可选的参数内置指针q,会令p指向q,否则会将p置空。 若还传递了参数d,将会调用d而不是delete来释放q |
不要混合使用普通指针和智能指针
//在函数被调用时ptr被创建并初始化
void process( shared_ptr ptr)
{
//使用ptr
} //ptr离开作用域被销毁
shared_ptr p(new int(42)); //引用计数:1
process(p); //拷贝增加引用计数,在process中引用计数:2
int i = *p; //正确:引用计数:1
int *x(new int(1024));
process(x); //错误:不能将int *转换成一个shared_ptr
process( shared_ptr(x)); //合法,但process结束后内存会释放
int j = *x; //未定义:x是一个空悬指针
当讲一个shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr。一旦这么做了,就不应该再使用内置指针来访问shared_ptr所指向的内存。
不要使用get初始化另一个只能指针或者为智能指针赋值
shared_ptr p(new int(42)); //引用计数:1
int *q = p.get(); //正确
{
//新程序块
//未定义:两个独立的shared_ptr指向相同内存
shared_ptr(q);
} //程序块结束,q被销毁,指向内存被释放
int foo = *p; //未定义:p指向内存已被释放
warning:get用来将指针的访问权限传递给代码,需确保代码不会delete指针。特别,永远不要用get获取的内置指针初始化另一个智能指针或者为另一个智能指针赋值
p = new int(1024); //错误:不能将一个内置指针赋予shared_ptr
p.reset( new int(2014)); //正确:p指向一个新的对象
if( !p.unique()) //不是唯一用户
p.reset( new string(*p)); //分配新的拷贝
*p += newVal; //现在我们是唯一用户
使用智能指针的一些基本规范:
由于unique_ptr拥有它指向的对象,因此不支持普通的拷贝和赋值操作
unique_ptr |
空unique_ptr,可以指向类型为T的对象,使用delete来释放指针 |
unique_ptr |
空unique_ptr,可以指向类型为T的对象,使用一个类型为D的可调用对象来释放指针 |
unique_ptr |
空unique_ptr,可以指向类型为T的对象,使用一个类型为D的可调用对象d来代替delete |
u = nullptr | 释放u指向的对象,将u置空 |
u.release() | u放弃对指针的控制权,返回指针,并将u置为空 |
u.reset() u.reset( q) u.reset( nullptr) |
释放u指向的对象 如果提供内置指针q,令u指向这个对象,否则将u置空 |
虽然不能拷贝或赋值unique_ptr,但可以通过release和reset来转移指针所有权:
//将所有权从p1转移给p2
//将p1置空
unique_ptr p2( p1.release() );
unique_ptr p3( new string("Trex");
//将所有权从p3转移给p2
//reset释放原p2指向的内存
p2.reset( p3.release() );
release会转移控制权,但不会释放内存。如果不用另一个智能指针来保存release返回的指针,我们的程序要负责资源的释放
p2.release(); //错误:p2不会释放内存,但指针丢失
auto p = p2.release(); //正确:但必须记得delete(p);
传递unique_ptr参数和返回unique_ptr
不能拷贝和赋值unique_ptr的规则有一个例外:可以拷贝和赋值一个将要被销毁的unique_ptr:
unique_ptr clone(int p)
{
return unique_ptr(p);
}
unique_ptr clone(int p)
{
unique_ptr ret(new int(p));
//……
return ret;
}
//p指向一个类型为objT的对象,并使用一个类型为delT的可调用对象来释放objT对象
//调用一个名叫fcn的delT类型对象
unique_ptr< objT, delT> p(new objT, fcn);
void f( destination &d /*其他需要参数*/ )
{
connection c = connect( &d );
unique_ptr p(&c, end_connection);
}
weal_ptr是一种不控制所指向对象生存期的智能指针,指向一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr对象上不会改变shared_ptr的引用计数。一旦最后一个share_ptr被销毁,对象就会被释放,即便有weak_ptr指向对象。
weak_ptr |
空weak_ptr可以指向类型为T的对象 |
weak_ptr |
与shared_ptr sp指向相同对象的weak_ptr。T类型必须能转换成sp指向的类型 |
w = p; | p可以是一个shared_ptr或一个weak_ptr。赋值后w和p共享对象 |
w.reset() | 将w置空 |
w.use_count() | 与w共享对象的shared_ptr的数量 |
w.expired() | 若w.use_count()为0,返回true。否则返回false |
w.lock() | 如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr |
由于对象可能不存在,所以不能使用weak_ptr直接访问对象,而必须调用lock。
if( shared_ptr np = wp.lock()) //如果np不会空则条件成立
{
}
核查指针类
#include
#include
#include
#incude
class StrBlobPtr
{
public:
StrBlobPtr():curr(0) {}
StrBlobPtr( StrBlob &a, size_t sz = 0):wptr(a.data),curr(sz) {}
std::string& deref() const;
StrBlobPtr& incr(); //
private:
//
std::shared_ptr> check(std::size_t, const std::string&) const;
std::weak_ptr< std::vector> wptr;
std::size_t curr;
};
std::shared_ptr> StrBlobPtr::check(std::size_t i, const std::string& msg) const
{
auto ret = wptr.lock();
if( !ret)
throw std::runtime_error("unbound StrBlobPtr");
if( i >= ret->size())
throw std::out_of_range(msg);
return ret;
}
std::string& StrBlobPtr::deref() const
{
auto p = check(curr, "deference past end ");
return (*p)[curr];
}
StrBlobPtr& StrBlobPtr::incr()
{
check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
new和delete运算符一次分配/释放一个对象,但某些应用需要一次为很多对象分配内存的功能。为支持这种功能,C++语言和标准库提供了两种一次分配一个对象数组的方法:
当使用一个类型别名来定义一个数组类型时,在new表达式中不使用[]。但在释放时必须使用方括号
typedef int arrT[42]; //arrT是43个int的数组的类型别名
int *p = new arrT;
delete [] p;
unique_ptr支持管理动态数组,shared_ptr不直接支持管理动态数组。如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器:
unique_ptr up(new int[43]);
up.release(); //自动调用delete []销毁指针
shared_ptr sp(new int[32],[](int *p){delete[] p;});
unique_ptr支持直接使用下标来管理动态分配的数组,shared_ptr不直接支持动态数组管理导致访问数组元素必须使用get获取内置指针来间接访问
for( size_t i = 0; i != 10; i++)
*(sp.get() + i) = i;
allocator |
定义了一个名叫a的allocator对象,可以为类型为T的对象分配内存 |
a.allocate(n) | 分配一段原始的、未构造的内存,保存n个类型为T的对象 |
a.deallocate(p, n) | 释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象: p必须是先前由allocator返回的指针,且n必须是p创建时所要求的的大小。 在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroy |
a.construct(p, args) | p必须是一个类型为T*的指针,指向一块原始内存:arg被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象 |
a.destroy(p) | p是T*类型的指针,此算法对p指向的对象执行析构函数 |
#include
allocator alloc;
auto const p = alloc.allocate(n); //分配n个未初始化的string
auto q = p; //q指向最后构造的元素之后的位置
alloc.construct(q++); //*q为空字符串
alloc.construct(q++, 10, 'c'); //*q为cccccccccc
alloc.construct(q++, "hi"); //*q为hi
warning:为了使用allocate返回的内存,我们必须使用construct构造函数。使用为构造的内存,其行为是未定义的
while( q!= p)
alloc.destroy( --q);
alloc.deallocate(p, n);
拷贝和填充未初始化内存的算法
这些函数在给定目的的位置创建元素,而不是由系统分配内存给它们 | |
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个对象。b指向的内存必须足够大,能容纳给定数量的对象 |
传递给uninitialized_copy的目的位置迭代器必须指向未构造的内存。与copy不同,uninitialized_copy在给定目的位置构造元素。返回(递增后)目的位置迭代器。
#include
vector vi({});
auto p = alloc.allocate( vi.size() * 2);
auto q = uninitialized_copy( vi.begin(), vi.end(), p);
uninitialized_fill_n( q, vi.size(), 42);