动态内存与智能指针
动态内存
在C++中,动态内存的管理是通过一对运算符来完成的: new, 在动态内存中为对象分配空间并返回一个指向该对象的指针,也可以选择对对象进行初始化 delete, 接受一个动态对象的指针,销毁该对象,并释放与之关联的内存
智能指针
为了更容易,也更安全的使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。智能指针的行为类似于常规指针,区别在于它负责自动释放所指向的对象。
含义
- shared_ptr 允许多个指针指向同一个对象
- unique_ptr 只能”独占”所指向的对象
初始化
智能指针也是一个模板。所以,创建智能指针时,必须提供指针类型,与vector一样,在尖括号内给出类型。
shared_ptr<std::string> p1; //p1指向string
shared_ptr<std::list<int>> p2; //p2指向list
智能指针默认初始化是一个空指针。
使用方式
//如果p1不为空,检查它是否指向一个空string。
if(p1 && p1->empty())
*p1 = "hi"; //如果p1指向一个空string,解引用p1,将一个新值赋予string
共同操作
操作函数 | 操作含义 | 是否支持 |
---|---|---|
shared_ptr<T> p | 空智能指针,可以指向类型为T的对象 | 都支持 |
unique_ptr<T> | ||
p | 将p用作一个条件判断,若p指向一个对象,则为true | |
*p | 解引用p, 获得它指向的对象 | |
p->mem | 等价于与(*p).mem | |
p.get() | 返回p中保存的指针(慎用) | |
swap(p, q) | 交换p 和 q 中指针 | |
p.swap(q) |
make_shared函数
它是一个标准库函数,定义在头文件memory中,在动态内存中分配一个对象并初始化它,返回指向该对象的shared_ptr。如下:
shared_ptr<int> p3 = make_shared<int>(43); //指向一个值为42的int的shared_ptr
shared_ptr<string> p4 = make_shared<string>(10, '9'); //p4指向一个值为“9999999999”的string
shared_ptr<int> p5 = make_shared<int>(); //p5指向一个值初始化的int,即值为0
调用make_shared
shared_ptr
当拷贝或赋值操作时,每个shared_ptr都会记录总共有多个shared_ptr指向这个相同的对象。如下:
shared_ptr<int> p = make_shared<int>(43); //p指向的对象只有p一个引用者
shared_ptr<int> q(p); //p和q指向相同对象,此对象有两个引用者
shared_ptr操作
| 操作函数 | 操作含义 | 是否支持 | | :—— | :——: | :——: | |make_shared<T>(args) | 返回一个shared_ptr,指向一个动态分配的类型为T的对象,并用args初始化该对象 | shared_ptr独有 | | shared_ptr<T> p(q) | p是shared_ptr的拷贝,此操作会递增q中的计数器。q中的指针必须能转换为T* | | p = q | p 和 q 都是shared_ptr,所保存的指针必须能相互转换。| | p.use_count() | 返回与p共享对象的智能指针数量;耗时,尽量用于调试 | | p.unique() | 若p.use_count()为1,返回true;否则返回false | 每个shared_ptr内部都有一个关联的计数器,通常称为引用计数(reference count)
计数器增加。如下:
- 用shared_ptr初始化另一个shared_ptr
- 作为参数传递给一个函数
- 作为函数的返回值
计数器递减。如下:
- 给shared_ptr赋予新值
- shared_ptr被销毁
- 局部的shared_ptr离开其作用域
当shared_ptr的计数器变为0时,它就会自动释放自己所管理的对象,调用自己的析构函数(destructor)来释放资源,完成销毁工作。
shared_ptr<int> r = make_shared<int>(43); //p指向的对象只有r一个引用者
r = q; //给r赋值,令它向另一个地址
//递增q指向的对象的引用计数
//递减r原来指向的对象的引用计数
//r原来指向的对象已没有引用者,会自动释放
class StrBlob
{
public:
using size_type = std::vector<std::string>::size_type;
StrBlob(): data(std::make_shared<std::vector<std::string>>()) {}
StrBlob(std::initializer_list<std::string> il):
data(std::make_shared<std::vector<std::string>>(il)){}
~StrBlob() {}
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()
{
check(0, "pop_back on empty StrBlob");
return data->pop_back();
}
const std::string& front()
{
check(0, "front on empty StrBlob");
return data->front();
}
const std::string& back()
{
check(0, "back on empty StrBlob");
return data->back();
}
private:
void check(size_type i, const std::string &msg) const
{
if(i >= data->size())
out_of_range(msg);
}
private:
std::shared_ptr<std::vector<std::string>> data;
};
unique_ptr
一个unique_ptr只能拥有一个对象,所以它不支持普通的拷贝和赋值操作,更改指向对象或者销毁unique_ptr时,指向的对象都会被销毁。初始化时,没有类似make_shared这样的标准库函数返回一个unique_ptr,所以必须采用直接初始化形式。
unique_ptr<double> p1;
unique_ptr<int> p2();
unique_ptr<int> p3(new int(42));
//unique_ptr<int> p4(p2); //错误:不支持拷贝
//unique_ptr<int> p5;
// p5 = p2; //错误:不支持赋值
unique_ptr操作
操作函数 | 操作含义 | 是否支持 |
---|---|---|
unique_ptr<T> u1 | 空unique_ptr,可以指向类型为T的对象。u1会使用delete来释放指针 | unique_ptr独有 |
unique_ptr<T, D> u2 | 如上,但是u2会使用一个类型为D的可调用对象来释放它的指针 | |
unique_ptr<T, D> u(d) | 空unique_ptr,用类型为D的对象d来代替delete | |
u = nullptr | 释放u指向的对象,将u置空 | |
u.release() | u放弃对指针的控制权, 返回指针,并将u置空 (如用指针变量接收这个返回值,切记要delete这个指针) | |
u.reset() | 释放u指向的对象 | |
u.reset(q) | 如果提供了内置指针q,令u指向这个对象;否则将u置空 | |
u.reset(nullptr) | 释放并置空 |
拷贝和赋值
unique_ptr<string> p2(p1.release()); //将p1指向的对象转移给p2,并将p1置空
unique_ptr<string> p3(new string("test"));
p2.reset(p3.release()); //将所有权从p3转移给p2, reset释放p2原来指向的对象
参数和返回值
不能拷贝的规则有一个例外:可以拷贝或赋值一个将要被销毁的unique_ptr。如下:
unique_ptr<int> clone(int p)
{
return unique_ptr<int>(new int(p));
}
unique_ptr<int> clone2(int p)
{
unique_ptr<int> ret(new int(p));
return ret;
}
删除器
shared_ptr和unique_ptr默认情况下都用delete释放它指向的对象。我们可以定义一个函数来代替delete,这个__删除器__函数必须能够完成对智能指针中保存的指针进行释放的操作,函数一般是单个参数。 shared_ptr和unique_ptr使用删除器的方式略有不同,shared_ptr不需要再定义是传入函数类型。
class Deleter
{
public:
void operator() (int* p)
{
cout << "delete p, *p=" << *p << endl;
delete p;
p = nullptr;
}
};
auto FuncDeleter = [](int *p)
{
cout << "delete p, *p=" << *p << endl;
delete p;
p = nullptr;
};
typedef void(*delType)(int *);
void test()
{
std::shared_ptr<int> s1(new int(1), FuncDeleter);
Deleter del;
std::shared_ptr<int> s2(new int(3), del);
std::unique_ptr<int, decltype(FuncDeleter)> u1(new int(4), FuncDeleter);
std::unique_ptr<int, delType> u2(new int(5), FuncDeleter);
std::unique_ptr<int, Deleter> u3(new int(6));
std::unique_ptr<int, Deleter> u4(new int(7), del);
}
weak_ptr
weak_ptr是一种不控制所指向对象生存期的指针指针,它指向一个有shared_ptr管理的对象。将一个weak_ptr绑定到shared_ptr不会改变shared_ptr的引用计数,而shared_ptr对象的引用计数变为0时,该对象会被销毁。即使weak_ptr指向它,对象还是被释放。weak_ptr就是智能指针的一种‘弱’共享对象。
操作方式
方式 | 含义 |
---|---|
weak_ptr<T> w | 空weak_ptr可以指向类型为T的对象 |
weak_ptr<T> 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 |