前言
本文介绍C++中的内存动态分配、和更好的用于对象内存管理的智能指针。
动态分配内存
-
对象生命周期
全局对象在程序启动时分配,在程序结束时销毁。
局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁。
局部static对象在第一次使用前分配,在程序结束时销毁。
动态分配的对象的生存期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会销毁。 -
分配在静态或栈内存中的对象由编译器自动创建和销毁。
静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量;static对象在使用之前分配,在程序结束时销毁。
栈内存用来保存定义在函数内的非static对象,对于栈对象,仅在其定义的程序块运行时才存在。 -
分配在堆内存中的对象由程序来控制其创建和销毁
用堆来存储动态分配(dynamically allocate)的对象——即,那些在程序运行时分配的对象,在堆(heap)内存上分配。动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显式地销毁它们。 -
动态内存的管理是通过一对运算符来完成的:
new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;
delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。 -
新标准库引入了智能指针:
shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。
shared_ptr
-
智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。
-
智能指针使用基本规范:
- 不要混合使用普通指针和智能指针
- 不要使用get()初始化另一个智能指针或为智能指针赋值
- 不使用相同的内置指针值初始化(或reset)多个智能指针。
- 不delete get()返回的指针。
- 不使用get()初始化或reset另一个智能指针。
- 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了。
- 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器
初始化
可以使用make_shared
或直接初始化初始化一个智能指针。
{
// 原始指针使用方式
MyString *p;
p = new MyString("p"); // 单参构造函数
delete p; // p~析构函数
p = nullptr; // 一定要重置为nullptr,减少出错
shared_ptr<MyString> p1; // 声明一个指针,未进行初始化时他是一个空指针,直接使用会npe
if (p1 == nullptr) {
cout << "npe" << endl; // npe,声明一个指针的时候默认值是nullptr
}
shared_ptr<MyString> p01 = shared_ptr<MyString>(new MyString("p01")); // 单参构造函数,
shared_ptr<MyString> p02 = make_shared<MyString>("p02"); // 单参构造函数,给该智能指针赋值
// shared_ptr<MyString> p11 = new MyString("p11"); // 错误,因为shared_ptr禁用了隐式类型转换
shared_ptr<MyString> p03(new MyString("p03")); // 正确,直接初始化,单参构造函数
}
// p03~析构函数
// p02~析构函数
// p01~析构函数
拷贝赋值
当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:
每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)。无论何时我们拷贝一个shared_ptr,计数器都会递增。
{
auto p1 = make_shared<MyString>("p1"); // 单参构造函数,给该智能指针赋值
cout << p1 << ", " << *p1 << ", " << p1.use_count() << endl; // 0x6000012544b8, p1, 1
shared_ptr<MyString> p2 = p1; // 当进行拷贝赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:
cout << p2 << ", " << *p2 << ", " << p1.use_count() << endl; // 0x6000012544b8, p1, 2
auto p3 = make_shared<MyString>("p3");
p3 = p1; // p3~析构函数,拷贝赋值会将原先的share_ptr持有的对象释放
cout << p3 << ", " << *p3 << ", "
<< p1.use_count() << ", " << p3.use_count() << endl; // 0x6000012544b8, p1, 3, 3
shared_ptr<MyString> p4(p3); // 当进行拷贝构造操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:
cout << p4 << ", " << *p4 << ", "
<< p1.use_count() << ", " << p3.use_count() << ", " << p4.use_count()
<< endl; // 0x6000012544b8, p1, 4, 4, 4
}
// p1~析构函数
unique_ptr
一个unique_ptr“拥有”它所指向的对象。
与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。
当unique_ptr被销毁时,它所指向的对象也被销毁。
没有类似make_shared的标准库函数返回一个unique_ptr。
unique_ptr不支持普通的拷贝或赋值操作。
{
unique_ptr<MyString> p1;
if (p1 == nullptr) {
cout << "npe" << endl; // npe
}
unique_ptr<MyString> p2(new MyString("p2")); // 直接初始化,unique_ptr也禁用了隐式类型转换,单参构造函数
// unique_ptr无法拷贝构造和拷贝赋值
// unique_ptr<MyString> p3(p2); // 错误,不支持拷贝
// unique_ptr<MyString> p3 = p2; // 错误,不支持赋值
// 可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique
unique_ptr<MyString> p3(p2.release()); // release将p2置为空
cout << p2 << "," << p3 << ", " << *p3 << endl; // 0x0,0x600000f58000, p2
}
// p2~析构函数
weak_ptr
-
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。
将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放;即使有weak_ptr指向对象,对象也还是会被释放。 -
不能使用weak_ptr直接访问对象,而必须调用lock。
此函数检查weak_ptr指向的对象是否仍存在。如果存在,lock返回一个指向共享对象的shared_ptr。
weak_ptr<MyString> pOuter;
{
auto p1 = make_shared<MyString>("p1");
pOuter = p1;
weak_ptr<MyString> wp(p1); // 直接初始化
weak_ptr<MyString> wp2 = p1; // 赋值初始化
cout << p1 << ", " << *p1 << ", " << p1.use_count() << ", " << wp.use_count() << endl; // 0x600001b45218, p1, 1, 1
// 我们不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。
// 如果存在,lock返回一个指向共享对象的shared_ptr。
auto p2 = wp.lock();
cout << (p1 == p2) << endl; // true
}
// p1~析构函数
cout << "weak_ptr<MyString> count: " << pOuter.use_count() << endl; // weak_ptr<MyString> count: 0
删除器
不是所有的类都有析构函数的定义的。特别是那些为C和C++两种语言设计的类,通常都要求用户显式地释放所使用的任何资源。
所以在使用智能指针时要释放这种资源,需要自定义删除器。
// test.h
class Connection {
public:
explicit Connection(string name) : _name(std::move(name)) {
}
~Connection() {
cout << _name << "~析构函数" << endl;
}
string get_name() const {
return _name;
}
private:
string _name;
};
inline void close(Connection *connection) {
cout << string("关闭") + connection->get_name() + "管理的连接中..." << endl;
}
// test.cpp
void Deleter(Connection *connection) {
close(connection);
delete connection;
}
void testDeleter() {
{
// 直接使用智能指针无法调用close函数
shared_ptr<Connection> pts = make_shared<Connection>("111");
unique_ptr<Connection> ptu(new Connection("222"));
}
// 222~析构函数
// 111~析构函数
{
shared_ptr<Connection> sp(new Connection("shared_ptr"), Deleter);
unique_ptr<Connection, decltype(Deleter) *> up(new Connection("unique_ptr"), Deleter);
}
// 关闭unique_ptr管理的连接中...
// unique_ptr~析构函数
// 关闭shared_ptr管理的连接中...
// shared_ptr~析构函数
auto DeleterLambda = [](Connection *connection) {
close(connection);
delete connection;
};
shared_ptr<Connection> sp2(new Connection("shared_ptr"), DeleterLambda);
unique_ptr<Connection, decltype(DeleterLambda)> up2(new Connection("unique_ptr"), DeleterLambda);
// 关闭unique_ptr管理的连接中...
// unique_ptr~析构函数
// 关闭shared_ptr管理的连接中...
// shared_ptr~析构函数
}