动态内存与智能指针
在c++中,动态内存的管理是通过一对运算符来完成的:new: 在动态内存中为对象分配空间并返回一个指向该对象的指针。delete: 接受一个动态对象的指针并销毁该对象,并释放与之关联的内存。
例子
//新建一个动态数组,元素个数为960*520,array是一个指针,指向数组的第一个int
int *array = new int[960*520];
delete [] array;//释放动态数组,方括号是必须的,因为我们当时分配的是一个数组
从上面的代码段可以看出,我们如果使用new来给对象动态分配空间的话,一定要记得对象用完之后使用delete释放空间,在这种情况下,会有内存泄露的风险。有时,在尚有指针引用内存的情况下,我们就释放了他,这个时候会产生引用非法内存的指针。
为了更安全的使用指针,标准库提供了智能指针 来管理动态对象。
1. shared_ptr: 允许多个指针指向同一个对象
2. unique_ptr: 指针独占所指向的对象
3. weak_ptr: 弱引用,指向shared_ptr 所指向的对象
这三种类型都定义在memory头文件中。
shared_ptr类
创建
类似vector,智能指针也是模板。所以我们在创建智能指针的时候,必须提供额外的信息—-智能指针所指向的类型。
例子
//动态分配内存,使p1指向值为hello world的string
shared_ptr<string> p1 = make_shared<string>("hello world");
//如果p1不为空,检查它指向的是不是一个空的string
if (p1 && p1->empty()){
*p1 = "string";//解引用p1,将一个新值赋予p1
}
//p2指向一个值为2的int
shared_ptr<int> p2 = make_shared<int>(2);
//p3 指向一个值初始化的int,即值为0
shared_ptr<int> p3 = make_shared<int>();
使用make_shared来创建智能指针时,其传递的参数必须与给定类型的对象的某个构造函数像匹配。
可以使用auto 来保存make_shared的结果:
auto p4 = make_shared<string>(10, '9');
拷贝和赋值
每个shared_ptr都有一个关联的计数器,通常称为引用计数。
拷贝一个智能指针的时候,会出现引用计数递增的情况:
1. 使用shared_ptr初始化另一个shared_ptr
2. 将其传递给另一个函数
3. 作为函数的返回值
计数器递减的情况:
1. 给shared_ptr赋予一个新值
2. shared_ptr被销毁(局部的shared_ptr离开其作用域)
auto p4 = make_shared<string>(10, '9');//p4指向的对象只有一个引用者
p1 = p4;//给p1赋值,另它指向另一地址(p1和p4指向同一个对象)
//递增p4指向对象的引用计数
//递减p1 原来指向对象的引用计数
//p1原来指向的对象已经没有引用者,会自动释放
当引用计数变为0时,shared_ptr会自动销毁此对象。是通过对象的析构函数完成销毁工作的。
class A{
public:
A(){
cout << "construct A" << endl;
}
~A(){
cout << "destruct A" << endl;
}
};
shared_ptr<A> getA(){
return make_shared<A>();
}
void dosthWithA(shared_ptr<A> a){
}
int main(){
shared_ptr<A> a = getA();
dosthWithA(a);
return 0;
}
可以看出,创建只能指针的时候,自动执行了函数的构造函数,当main函数执行完毕之后,再也没有人引用A对象了,自动调用析构函数,销毁对象。
使用了动态生存期资源的类
程序使用动态内存一般有以下几种原因:
1. 程序不知道自己使用多少对象
2. 程序不知道所需对象的准确类型
3. 程序需要在多个对象间共享数据
容器类是出于第一种原因而是用动态内存的例子。
对象共享数据:
vector<string> v1;
{
vector<string> v2 = { "a", "b", "c" };
v1 = v2;//从v2拷贝元素到v1中
}//v2被销毁,其中的元素也被销毁
//v1有三个元素,是原来v2中元素的拷贝
由一个vector分配的元素只有当这个vector存在的时候才存在。当一个vector销毁的时候,这个vector中的元素也被销毁。
为了共享对象数据,我们希望原对象机器拷贝应该引用相同的底层元素。比如说下面的代码:
Blog<string>b1;
{
Blob<string>b2 = { "a", "b" };
b1 = b2;//b1和b2共享相同的元素
}//b2销毁了,但是b2中的元素不能销毁
//b1指向最初由b2创建的元素
但是该如何实现呢?为了保证b2离开其作用域时,其中的vector的元素继续存在不被销毁,我们将vector保存在动态内存中。
class StrBlob {
public:
typedef std::vector<std::string>::size_type size_type;
// constructors
StrBlob() : data(std::make_shared<std::vector<std::string>>()) { }
StrBlob(std::initializer_list<std::string> il);
// size operations
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类只有一个数据成员,它是shared_ptr类型。当我们拷贝、赋值或销毁一个StrBlob对象时,它的shared_ptr成员也会被拷贝、赋值或销毁。对于由StrBlob函数构造分配的vector,当最后一个指向他的StrBlob对象被销毁时,它会随之被自动销毁。