以对象管理资源
如果直接使用delete
对资源进行释放,会因异常情况导致控制流不会到达delete
语句而无法完成资源释放。我们利用对象生命周期结束自动析构这一点,将资源生命周期和对象生命周期进行绑定,从而确保资源正确释放。这类用于绑定资源生命周期的类叫资源管理类。管理内存对象的资源管理类通常是智能指针。
智能指针
- unique_ptr
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> pointer(new int(4));
std::cout << *pointer << std::endl;
}
unique_ptr
可以管理一个对象的资源,但是不允许对其进行复制。你可以使用move
移动其所有权。
std::unique_ptr<int> pointer2(std::move(pointer));
- shared_ptr
由于unique_ptr
不允许复制,但是复制的需求确实存在,所以出现了引用计数智能指针shared_ptr
。
shared_ptr
是允许复制和赋值的,这类操作会使得shared_ptr
计数增加,当最后一个shared_ptr
被销毁时(即计数降为0时),它才会释放所管理的资源。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr1(new int(5));
std::cout << "ptr1: " << *ptr1 << ", use_count: " << ptr1.use_count() << std::endl;
{
std::shared_ptr<int> ptr2 = ptr1;
std::cout << "ptr2: " << *ptr2 << ", use_count: " << ptr2.use_count() << std::endl;
}
std::cout << "ptr1: " << *ptr1 << ", use_count: " << ptr1.use_count() << std::endl;
return 0;
}
智能指针的陷阱
- 陷阱1:
shared_ptr
如果出现相互引用的情况,就会导致计数永远不会减少为0,从而导致内存泄漏。
示例如下:
#include <memory>
struct B; // 前向声明
struct A {
std::shared_ptr<B> b_ptr;
};
struct B {
std::shared_ptr<A> a_ptr;
};
int main() {
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
return 0;
}
使用内存泄漏分析工具valgrind
:
g++ -g sharedptr.cpp -o sharedptr
valgrind --leak-check=full ./sharedptr
发现内存泄漏:
==588256== LEAK SUMMARY:
==588256== definitely lost: 32 bytes in 1 blocks
==588256== indirectly lost: 32 bytes in 1 blocks
==588256== possibly lost: 0 bytes in 0 blocks
==588256== still reachable: 0 bytes in 0 blocks
==588256== suppressed: 0 bytes in 0 blocks
我们可以使用weak_ptr
来处理这种情况。weak_ptr
只指向shared_ptr
管理的对象,但不会增加其引用计数,当最后一个shared_ptr
被销毁时,weak_ptr
所指向的对象就会被销毁,即使weak_ptr
还指向它。
// ...
struct B {
// 修改
std::weak_ptr<A> a_ptr;
};
// ...
内存泄漏问题解决:
==589233== All heap blocks were freed -- no leaks are possible
- 陷阱2:
unique_ptr
和shared_ptr
在其析构函数中使用的使用delete
而不是delete[]
。
这就意味着用它们管理动态分配的数组并不合适。
对于一些简单的数据类型,如int
、char
等,并不会造成内存泄漏,因为对它们而言delete
和delete[]
都能接受。
但是对于一些复杂的数据结构,如string
,就会出现内存泄漏的情况。
#include<memory>
#include<string>
int main() {
std::unique_ptr<std::string> aps(new std::string[10]);
}
使用内存泄漏分析工具valgrind
:
g++ -g ptrdelete.cpp -o ptrdelete
valgrind --leak-check=full ./ptrdelete
发现出现内存泄漏:
==552990== LEAK SUMMARY:
==552990== definitely lost: 328 bytes in 1 blocks
==552990== indirectly lost: 0 bytes in 0 blocks
==552990== possibly lost: 0 bytes in 0 blocks
==552990== still reachable: 0 bytes in 0 blocks
==552990== suppressed: 0 bytes in 0 blocks
自定义类型Object
:
#include<iostream>
#include<memory>
#include<string>
using namespace std;
class Object {
public:
Object(int len)
: _len(len) {
// 使用new[]分配内存
c_buffer = new char[_len];
}
~Object() {
// 使用delete[]释放内存
delete[] c_buffer;
}
private:
// 字符数组
char * c_buffer;
int _len;
};
int main() {
// 使用unique_ptr管理Object数组对象。
std::unique_ptr<Object> spi(new Object[5]{5, 5, 5, 5, 5});
}
同样会出现内存泄漏:
==556994== LEAK SUMMARY:
==556994== definitely lost: 88 bytes in 1 blocks
==556994== indirectly lost: 20 bytes in 4 blocks
==556994== possibly lost: 0 bytes in 0 blocks
==556994== still reachable: 0 bytes in 0 blocks
==556994== suppressed: 0 bytes in 0 blocks
所以我们应该使用vector
这类容器来取代动态分配的数组,而非使用智能指针来管理动态分配的数组。