C++动态内存和智能指针

前言

对象有生命周期,C++对象的生命周期与存储位置是息息相关的。

C++对象按照创建位置可以分为全局对象、局部对象、局部静态对象。他们的生命周期是固定的,由编译器创建和销毁。另外,C++支持动态分配对象,动态对象的分配和释放需要程序显示的操作。

C++内存分为静态存储区栈内存堆内存三种。

  1. 静态内存:保存局部static对象、类static数据成员、全局变量
  2. 栈内存:保存函数内定义的非static对象
  3. 堆内存:存储动态分配的对象

静态内存随程序启动直到程序结束为止,栈内存随方法调用直到方法返回结束,堆内存随程序分配到程序释放为止(最后一定会被系统收回)。

动态内存

C++最常用的内存管理是通过 new / delete 来完成的。new,在动态内存为对象分配空间并返回一个该对象的指针,我们可以选择对对象进行初始化。delete,接受一个动态对象的指针,销毁对象,并释放内存。注意,这里每个操作都有两个子操作

  1. new : 分配空间,初始化
  2. delete : 销毁对象,释放空间

新的标准库提供了分别进行子操作的能力,能够提高性能,后面会介绍。

注意:

默认情况下,new 动态分配的对象是默认初始化的,即内置对象值是未定义的,类类型对象使用默认构造函数

注意:

内置指针管理的动态内存直到被显示释放之前会一直存在

//new 
int *pi = new int; // pi 指向动态分配的,未初始化的无名对象
string *ps = new string; // 初始化为空串

int *pi = new int(1024); // 直接初始化
string *ps = new string(10, '9'); // "9999999999"
vector<int> *pv = new vector<int>{0, 1, 2, 3, 4};

int *pi = new int(); // 值初始化为0

//delete 
delete pi; // 传递给delete的指针必须是指向动态分配的内存或者空指针
delete ps;
delete pv;

const int *pci = new const int(1024);
delete pci; // const对象值不能改变,但是本身可以被销毁

//数组
int *pia = new int[get_size()]; // pia 指向第一个int
int *pia2 = new int[10](); // 初始化为0
int *pia3 = new int[10]{0, 1} // 0, 1, 0, 0, 0, 0...

char arr[0]; // 错误:不能定义长度为0的数组
char *cp = new char[0]; // 正确:但cp不能解引用

delete [] pa;

容易出错的三点:

  1. 忘记delete
  2. 使用已经释放的对象(一定要记得释放之后设置为空指针)
  3. 同一块内存释放两次

智能指针

前面我们提到使用内置的new / delete操作动态内存,会导致一些问题,这些问题主要是程序员的错误导致的。另外,动态分配内存会绑定分配空间和初始化的操作。特定的场景下性能不佳。因此,C++新的标准库为我们提供了智能指针。

需要提一下的是,有一些其他的著名的C++库也提供智能指针,比如boost library,这里就不对他们进行分析了。工程上遇到了问题再去了解也不迟。

C++primer提到的标准库两种智能指针,shared_ptrunique_ptr, 还有一个伴随类weak_ptrshared_ptr允许多个指针指向同一对象,unique_ptr则“独占”所指向的对象。三种类型都定义在memory头文件里

  • shared_ptr 类
#include <memory>

shared_ptr<string> p1; // 空智能指针
if (p1) {} // p指向一个对象,则为true
*p; // 解引用
p.get(); // 返回p中保存的指针
swap(p, q); // 交换指针
p.swap(q);

auto p = make_shared<T>(arg); // 返回一个shared_ptr,指向用args初始化的对象
auto q = shared_ptr(p); // q是p的拷贝;增加p的计数器
p = q; // 递减p的计数器,增加q的计数器,若计数器为0,则释放内存
p.unique(); // 是否只有一个引用
p.use_count(); // 返回共享对象的智能指针数量,可能很慢
  • unique_ptr 类
#include <memory>

unique_ptr<string> p1;
unique_ptr<string> p2(new int(42));

unique_ptr<string> p3(p2); // 错误,unique_str独占对象,因此没有拷贝或者复制操作

unique_ptr<string> p3 = p2; // 错误

u = nullptr; // 释放对象,将u置空
u.release(); // u放弃对指针的控制权,返回指针,并将u置空
u.reset(); // 释放对象,将u置空
u.reset(q); // q是内置指针

p2.reset(p3.release()); // 将p3 转移给p2 
  • allocator 类
#include <memory>

allocator<string> alloc; // 可以分配string的allocator对象
auto const p = alloc.allocate(n); // 分配n个未初始化的string

auto q = p; // q指向未构造空间的首地址
alloc.construct(q ++);
alloc.construct(q ++, 10, 'c');
alloc.construct(q ++, "hi");

cout << *p;

while(q != p) // 释放空间,首先destroy,然后deallocate
    alloc.destory(-- q);
alloc.deallocate(p, n);

auto p = alloc.allocate(vi.size() * 2); // 拷贝一个vector数组为两倍,并在后面填充42
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
uninitialized_fill_n(q, vi.size(), 42);

智能指针使得动态内存管理更加安全,应该尽可能的使用智能指针,同时也可以自己设计智能指针模拟指针的行为。主要在包装类中设置一个引用计数器,在析构函数中递减引用计数器,当引用计数器为0则释放内存。拷贝构造时,增加引用计数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值