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

C++中,动态内存的管理是通过:new在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
动态内存很容易出问题,因为确保在正确的时机释放内存很困难。有时会忘了释放内存,产生内存泄露;有时还有指针引用内存就释放了它,会产生UAF。
为了更容易更安全地使用动态内存,新的标准库提供了两种智能指针(smart pointer)类型来管理动态对象。这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则独占所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。

shared_ptr类

类似vector,智能指针也是模板。创建一个智能指针时,必须提供额外信息(指针指向的类型)

1
shared_ptr<string> p1; // shared_ptr,可以指向string

shared_ptr和unique_ptr都支持的操作

操作 作用
p 将p用作一个条件判断,若p指向一个对象,则为true
*p 解引用p,获得它指向的对象
p->mem 等价于(*p).mem
p.get() 返回p中保存的指针
swap(p, q) 交换p和q中的指针

shared_ptr独有的操作

make_shared函数,在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// main.cpp
include "student.h"
#include <memory>
int main()
{

vector<float> ivec = {95.5, 97.0, 94.6, 98.7};

auto sp = make_shared<Student>(1, "brucefan", ivec);
cout << sp->getName() << endl;

shared_ptr<Student> sp(new Student(2, "fanrong", ivec));
cout << sp->getName() << endl;

return 0;
}
// student.h
include <iostream>
#include <string>
#include <vector>
using namespace std;

class Student {
public:
Student(int i, string n, vector<float> &s): id(i), name(n),
score(s) { }
int getId();
string getName();
vector<float> getScore();
private:
int id;
string name;
vector<float> score;
};
// student.cpp
#include "student.h"
int Student::getId() {
return id;
}
string Student::getName() {
return name;
}
vector<float> Student::getScore() {
return score;
}

每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)
拷贝一个shared_ptr时,计数器都会递增。如:当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。
当给shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减。
一旦一个shared_ptr的计数器变为0,他就会自动释放自己所管理的对象。

unique_ptr类

与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁。与shared_ptr不同,没有类似make_shared的标准库函数返回一个unique_ptr。定义一个unique_ptr时,需要将其绑定到一个new返回的指针上:

1
2
unique_ptr<double> doup(new double(3.14));
unique_ptr<string> strp(new string("brucefan"));

由于unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作。但可以通过调用releasereset将指针的所有权从一个(非const)unique_ptr转移给另一个unique:

1
2
3
4
5
// 将所有权从strp转移给strp2
unique_ptr<string> strp2(p1.release()); // release将p1置为空
unique_ptr<string> strp3(new string("fanrong"));
// 将所有权从strp3转移给p2
p2.reset(p3.release()) // reset释放了p2原来指向的内存

release成员返回unique_ptr当前保存的指针,并将其置空。因此strp2被初始化为strp1原来的指针,strp1被置空。
reset成员接受一个可选的指针参数,令unique_ptr重新指向给定的指针,原来指向的对象被释放。

weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。最后一个shared_ptr被销毁,对象就会被销毁,即使还有weak_ptr指向这个对象。

1
2
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp弱共享p;p的引用计数未改变