cpp primer:动态内存

本文详细介绍了C++中动态内存管理和智能指针(shared_ptr、unique_ptr、weak_ptr)的使用,包括make_shared函数、内存分配与释放、异常处理以及标准库容器的应用。特别强调了使用智能指针避免内存泄漏和资源管理的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


目前为止:

  • 全局对象在程序启动时候分配,结束时候销毁。
  • 局部自动对象,块内定义,离开块销毁。
  • 局部static对象第一次使用分配,结束时销毁。

目前只使用过静态内存和栈内存。
静态内存

  • 局部static对象
  • 类static数据成员
  • 任何函数之外的变量
    栈内存
  • 函数内的非static对象

接下来就开始介绍

动态内存与智能指针

shared_ptr类

make_shared函数

shared_ptr的拷贝和赋值

shared_ptr自动销毁所管理的对象
通过析构函数!
shared_ptr还会自动释放相关联的内存
动态对象不再使用就会释放相关联的内存。

// factory returns a shared_ptr pointing to a dynamically allocated object
shared_ptr<Foo> factory(T arg)
{
 	// process arg as appropriate
 	// shared_ptr will take care of deleting this memory
 	return make_shared<Foo>(arg);
}

像下面的这两种种就可以安全的使用:

void use_factory(T arg)
{
 	shared_ptr<Foo> p = factory(arg);
 	// use p
} 	// p goes out of scope; the memory to which p points is automatically freed
shared_ptr<Foo> use_factory(T arg)
{
 	shared_ptr<Foo> p = factory(arg);
 	// use p
 	return p; 	// reference count is incremented when we return p
}				// p goes out of scope; the memory to which p points is not freed

使用动态生存期的类
出于以下三种原因

  • 程序不知道使用多少对象:容器类
  • 程序不知道对象的准确类型
  • 程序需要在多个对象间共享数据:如共享reference count

下面定义一个共享数据的类(拷贝,赋值,销毁都使用默认的就足够了)

class StrBlob {
public:
 	typedef std::vector<std::string>::size_type size_type; 
 	StrBlob(); 
 	StrBlob(std::initializer_list<std::string> il);  //可以列表初始化!
 	size_type size() const { return data->size(); } 
 	bool empty() const { return data->empty(); }
 	// add and remove elements
 	void push_back(const std::string &t) {data->push_back(t);}
 	void pop_back();
 	// element access
 	std::string& front(); std::string& back();
private:
 	std::shared_ptr<std::vector<std::string>> data;
 	// throws msg if data[i] isn't valid
 	void check(size_type i, const std::string &msg) const;
};

StrBlob::StrBlob(): data(make_shared<vector<string>>()) { }
StrBlob::StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)) { }
 
void StrBlob::check(size_type i, const string &msg) const
{
 	if (i >= data->size()) throw out_of_range(msg);
}

string& StrBlob::front()
{
 	// if the vector is empty, check will throw
 	check(0, "front on empty StrBlob"); return 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();
}

注意

  1. 理解make_shared函数的意义,shared_ptr的意义。
  2. 如果将shared_ptr放在容器里,而后不再需要,一定用erase删除,不然内存得不到释放。
  3. shared_ptr管理栈内指针和堆指针的区别(shared_ptr能管理栈内指针吗?栈内的内存需要管理吗?)不能,不需要
  4. shared_ptr除了默认构造为空和使用make_shared的拷贝构造,还有其他的吗?有,但是还是使用new来初始化,依然是管理动态内存。

直接内存管理

与智能指针不同,自己管理内存的类不能依赖类对象拷贝,赋值,销毁的任何默认定义。

可以使用类型推断

auto p1 = new auto(obj); 	// p points to an object of the type of obj
 							// that object is initialized from obj
auto p2 = new auto{a,b,c}; 	// error: must use parentheses for the initializer

动态分配const对象

// allocate and initialize a const int
const int *pci = new const int(1024);

内存耗尽
两种处理方式

// if allocation fails, new returns a null pointer
int *p1 = new int; // if allocation fails, new throws std::bad_alloc
int *p2 = new (nothrow) int; // if allocation fails, new returns a null
pointer

释放错误
下面的例子反映了如果不返回动态内存的指针(非智能指针)会造成什么负担

// factory returns a pointer to a dynamically allocated object
Foo* factory(T arg)
{
 // process arg as appropriate
 return new Foo(arg); // caller is responsible for deleting this memory
}

void use_factory(T arg)
{
 Foo *p = factory(arg);  //如果不释放就再也不能被释放
 // use p but do not delete it
} // p goes out of scope, but the memory to which p points is not freed!

delete之后重置指针值
delete之后很多都变成空悬指针,我们再指针离开作用域前delete指针再置为nullptr可以部分解决这个问题。所以也没有什么卵用。

注意

  1. 值初始化和默认初始化在内置类型的含义是不同的,但是在类类型是相同的,都使用默认构造函数。
string *ps1 = new string; 	// default initialized to the empty string
string *ps = new string(); 	// value initialized to the empty string
int *pi1 = new int; 		// default initialized; *pi1 is undefined
int *pi2 = new int(); 		// value initialized to 0; *pi2 is 0
  1. new 和delete都有两个步骤:1对象的创建与销毁2分配和释放内存
  2. 使用new和delete容易1忘记detele.2释放两次3使用已经释放的对象。所以要只使用智能指针!

shared_ptr和new结合使用

智能指针的构造函数是explicit的,不能隐式转换。

shared_ptr<int> p1 = new int(1024); // error: must use direct
initialization
shared_ptr<int> p2(new int(1024)); // ok: uses direct initialization

reset的使用
类似与反向赋值,通常与unique结合使用在改变底层对象之前确认自己是不是仅有的用户

if (!p.unique())
 	p.reset(new string(*p)); 	// we aren't alone; allocate a new copy
*p += newVal; 					// now that we know we're the only pointer, okay to change this
object

注意

  1. 初始化智能指针的普通指针必须指向动态内存。
  2. 不要混用智能指针和普通指针。(下面的例子很重要
int *x(new int(1024)); // dangerous: x is a plain pointer, not a smart
pointer
process(shared_ptr<int>(x)); // legal, but the memory will be deleted!
int j = *x; // undefined: x is a dangling pointer!
  1. 不要使用智能指针的get方法初始化或赋值一个智能指针。get的目的是为了兼容不能使用智能指针的代码。

智能指针与异常

使用智能指针在异常发送的时候也能释放资源!

智能指针和哑类
一些c和c++共同使用的库怎么去释放资源呢?c并没有析构函数。

struct destination; 				// represents what we are connecting to
struct connection; 					// information needed to use the connection
connection connect(destination*); 	// open the connection
void disconnect(connection); 		// close the given connection
void f(destination &d /* other parameters */)
{
 	// get a connection; must remember to close it when done
 	connection c = connect(&d);
 	// use the connection
 	// if we forget to call disconnect before exiting f, there will be no way to close c
}

上面的遗忘使用disconnect释放连接,和使用shared_ptr避免资源没有释放是等价的。所以我们可以定义自己的delete操作。

自定义shared_ptr的释放操作
默认释放是delete,,但是我们可以自定义删除器。

//自定义的删除器
void end_connection(connection *p) { disconnect(*p); }


void f(destination &d /* other parameters */)
{
 	connection c = connect(&d); 
 	shared_ptr<connection> p(&c, end_connection); //另一种初始化方式,第二个参数是可调用对象 
 	// use the connection
 	// when f exits, even if by an exception, the connection will be properly closed
}

unique_ptr

unique_ptr不支持拷贝和赋值操作。但支持更换所有权:

p2.reset(p3.release()); // reset deletes the memory to which p2 had
pointed

传递和返回unique_ptr
可以拷贝和赋值一个将要被销毁的unique_ptr。

unique_ptr<int> clone(int p) { 
	unique_ptr<int> ret(new int (p)); 
	// . . . 
	return ret;
}

这里编译器执行的是移动赋值和移动构造

传递删除器
unique的删除器会影响到reset(构造)该类型的对象。

void f(destination &d /* other needed parameters */)
{
 	connection c = connect(&d); // open the connection
 	// when p is destroyed, the connection will be closed
	 unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
 	// use the connection
 	// when f exits, even if by an exception, the connection will be properly closed
}

注意

  1. unique和shared的ptr的管理删除器的方式不同(见16.1.6)

weak_ptr

我们不能直接访问wp,通过lock()确认对象存在再访问。

if (shared_ptr<int> np = wp.lock()) { // true if np is not null
 // inside the if, np shares its object with p
}

核查指针类
作为wp功能的展示,定义一个指针类StrBlobPtr,保存wp指向StrBlobdata成员。使用wp阻止用户访问不存在的vector的企图。

weak_ptr不影响一个给定StrBlob的生存期,但是可以阻止用户访问一个不再存在vector的企图。

// StrBlobPtr throws an exception on attempts to access a nonexistent element
class StrBlobPtr {
public:
 	StrBlobPtr(): curr(0) { } 
 	//不能用const StrBlob初始化
 	StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { } 
 	//定义的运算符解引用和前缀递增
 	std::string& deref() const;
 	StrBlobPtr& incr(); // prefix version
private:
 	// check returns a shared_ptr to the vector if the check succeeds
 	std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string&) const;
 	// store a weak_ptr, which means the underlying vector might be destroyed
 	std::weak_ptr<std::vector<std::string>> wptr;
 	std::size_t curr; // current position within the array
};

//检查指针指向的vector是否存在
std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(std::size_t i, const std::string &msg)
const
{
 	auto ret = wptr.lock(); // is the vector still around?
 	if (!ret) 
 		throw std::runtime_error("unbound StrBlobPtr"); 
 	if (i >= ret->size()) 
 		throw std::out_of_range(msg);
 	return ret; // 否则返回vector的shared_ptr
}

//下面定义StrBlobPtr的指针操作
std::string& StrBlobPtr::deref() const
{
 	auto p = check(curr, "dereference past end");
 	return (*p)[curr]; // (*p) is the vector to which this object points
}

// prefix: return a reference to the incremented object
StrBlobPtr& StrBlobPtr::incr()
{
 	// if curr already points past the end of the container, can't increment it
 	check(curr, "increment past end of StrBlobPtr");
 	++curr; // advance the current state
 	return *this;
}

为了能够访问data成员,还要声明friend,且定义begin()end()操作

// forward declaration needed for friend declaration in StrBlob
class StrBlobPtr;
class StrBlob {
 	friend class StrBlobPtr;
 	// other members as in § 12.1.1 
 	// return StrBlobPtr to the first and one past the last elements
 	StrBlobPtr begin() { return StrBlobPtr(*this); } 
 	StrBlobPtr end() 
 		{ 	auto ret = StrBlobPtr(*this, data->size()); 
 			return ret; }
};

begin和end可以看到我们定义的伴随类的作用。那么问题来了,如果不定义伴随类还有别的方法来定义begin和end吗?

动态数组

大多数的类应该使用标准库容器而不是动态分配的数组,自己管理动态内存可能会出现问题,在学完拷贝控制之前不要在类内分配动态内存。

new和数组

初始化动态分配对象的数组

int *pia = new int[10]; 	// block of ten uninitialized ints
int *pia2 = new int[10](); 	// block of ten ints value initialized to 0

// block of ten ints each initialized from the corresponding initializer
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};

释放

delete [] p;

智能指针和动态数组
标准库提供了可以管理new分配的数组的unique_ptr的版本。

// up points to an array of ten uninitialized ints
unique_ptr<int[]> up(new int[10]);
//自动调用delete []
up.release(); // automatically uses delete[] to destroy its pointer

//可以使用下标操作了
for (size_t i = 0; i != 10; ++i)
	up[i] = i; // assign a new value to each of the elements

注意

  1. 动态分配空数组也是合法的。
  2. 注意指向数组的unique_ptr的操作与前面的有所不同。
  3. shared_ptr并不支持管理动态数组,如果想使用的话就提供自己定义的删除器。但是还是不能使用下标运算符。
  4. 智能指针不支持指针算术运算

allocator类

把new和delete中的两个步骤(管理内存,构造/析构对象)解耦,所以有allocate,deallocate,construce,destroy

下面是用法:

allocator<string> alloc; 			// object that can allocate strings
auto const p = alloc.allocate(n); 	// allocate n unconstructed strings

//如何构造
auto q = p; 					// q will point to one past the last constructed element
alloc.construct(q++); 			// *q is the empty string
alloc.construct(q++, 10, 'c'); 	// *q is cccccccccc
alloc.construct(q++, "hi"); 	// *q is hi!

//如何析构
while (q != p)
 	alloc.destroy(--q); // free the strings we actually allocated

//传入的n必须与allocate的一样
alloc.deallocate(p, n);

拷贝和填充未初始化内存的算法
提供了便捷的初始化的方法。有四种:uninitialized_copy/_copy_n/_fill/_fill_n的算法。接受迭代器

使用标准库:文本查询程序

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值