C++Primer_Chap12_动态内存_笔记

静态内存:保存局部static对象、类static数据成员以及定义在任何函数之外的变量。

栈内存:   保存定义在函数内的非static对象

动态内存(堆):   存储动态分配的对象

动态内存和智能指针

    为了更安全的使用动态内存,,新的标准库提供了两种智能指针(smart pointer,定义在memory头文件中),与常规指针的区别在于负责自动释放所指向的对象。两种智能指针的区别在于管理底层指针的方式:

  • shared_ptr允许多个指针指向同一个对象
  • unique_ptr"独占"所指向的对象
  • weak_ptr:伴随类,一种弱引用,指向share_ptr所管理的对象。
#include 
shared_ptr和unique_ptr都支持的操作

shared_ptr sp

unique_ptr up

空智能指针,可以指向类型为T的对象
p 将p作为一个条件判断,若p指向一个对象,则为true
*p 解引用p,或得它指向的对象
p->mem 等价(*p).mem
p.get() 返回p中保存的指针(内置指针,且不能delete此指针)。要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了。

swap( p, q)

p.swap(q)

交互p和q中的指针

share_ptr类

 

share_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的引用计数变为0,则将其管理的原内存释放
p.unique() 若p.use_count()为1,返回true。否则返回false
p.use_count() 返回与p共享对象的只能指针数量:可能很慢。主要用于调试

  每个shared_ptr都有一个关联的计数器,通常称为引用计数(reference count)

  • 当拷贝一个shared_ptr时,计数器会递增。如:用一个shared_ptr初始化另一个shared_ptr,将它作为参数传递给一个函数以及作为函数的返回值
  • 当给share_ptr赋予一个新值或是shared_ptr被销毁(比如一个局部的shared_ptr离开其作用域),计数器会递减
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的其他方法
shared_ptr p(q) p管理内置指针q所指向的对象;q必须指向new分配的内存,且能转换为T*类型
shared_ptr p(u) p从unique_ptr u那里接管了对象的所有权;将u置位空
shared_ptr p(q,d) p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用对象d来代替delete
shared_ptr p( p2,d) 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;			//现在我们是唯一用户

使用智能指针的一些基本规范:

  • 不能使用相同的内置指针初始化(或reset)多个智能指针
  • 不delete get()返回的指针
  • 不使用用get(0初始化或rest另一个智能指针
  • 如果使用了get()返回的指针,当最后一个对应的智能指针销毁后,指针将无效
  • 如果使用智能指针管理的资源不是new分配的内存,记得传递给它一个删除函数

unique_ptr

  由于unique_ptr拥有它指向的对象,因此不支持普通的拷贝和赋值操作

unique_ptr的操作
unique_ptr u 空unique_ptr,可以指向类型为T的对象,使用delete来释放指针
unique_ptr u 空unique_ptr,可以指向类型为T的对象,使用一个类型为D的可调用对象来释放指针
unique_ptr u(d) 空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);
}

weak_ptr

  weal_ptr是一种不控制所指向对象生存期的智能指针,指向一个shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr对象上不会改变shared_ptr的引用计数。一旦最后一个share_ptr被销毁,对象就会被释放,即便有weak_ptr指向对象。

weak_ptr
weak_ptr w 空weak_ptr可以指向类型为T的对象
weak_ptr w(sp) 与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++语言和标准库提供了两种一次分配一个对象数组的方法:

  1. C++语言定义了另一种new表达式语法,可以分配并初始化一个对象数组
  2. 标准库中包括一个名为allocator的类,允许将分配和初始化分离

  当使用一个类型别名来定义一个数组类型时,在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类

标准库allocator类及其算法
allocator a 定义了一个名叫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);

拷贝和填充未初始化内存的算法 

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个对象。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);

 

你可能感兴趣的:(C++)