智能指针

引言

对于C和C++语言,指针有着独特的作用。通过指针的使用,可以避免很多的拷贝操作,提升性能。但是普通的指针使用不当的话,造成内存泄漏,会导致更加严重的后果。C++为了解决这些问题,提出了引用的技术,然而这依旧不能解决所有的问题,因此c++提出了智能指针技术。

所谓智能指针是指行为类似常规指针,但是它负责自动释放所指向的对象。

新的标准库提供了两种职能指针,分别为shared_ptr和unique_ptr

shared_ptr允许多个指针指向同一个对象

unique_ptr则“独占”所指向的对象

shared_ptr

通过下面的方式可以定义一个空的智能指针

1
shared_ptr<T> sp;

我们还可以定义一个指向固定内存的职能指针

1
shared_ptr<int> sp(new int(42));

智能指针只能使用直接初始化的方式,而不能使用赋值操作符。如下的操作时非法的

1
shared_ptr<int> sp = new int(42); // error

同样的道理,这样返回也是不行的

1
2
3
shared_ptr<int> clone(int p) {
return new int(p); // error
}

因为智能指针和普通的指针不能混用

而只能用下面的形式

1
2
3
shared_ptr<int> clone(int p) {
return shared_ptr<int>(new int(p));
}

make_shared

智能指针只能用直接初始化的方式来创建,但是这种形式实际上是不高效的,标准库提供了更加高效的方式,即make_shared

1
2
3
auto p = make_shared<int>(42);
auto p1 = make_shared<vector<string>>()
auto q(p); // p和q指向同一个对象,此对象有两个引用者

shared_ptr的基本操作

1
2
3
4
5
6
7
8
9
10
11
shared_ptr<T> p    // 初始化一个空的智能指针,指向T
p // 在用指针之前一般要做下非空判断
*p // 解引用
p.get() // 返回p中的指针
swap(p, q) // 交换p和q中的指针

make_shared<T>(args) // 返回一个shared_ptr
shared_ptr<T> p(q) // p是智能指针q的一个拷贝
p = q // p和q是能够相互转换的智能指针,此操作p的引用计数递减,q的引用计数递增
p.unique() // 判断p的引用计数是否为1
p.use_count() // 返回与p共享对象的智能指针数量,一般用于调试,性能较差

unique_ptr

unique_ptr与shared_ptr不同,同一时刻只能有一个unique_ptr指向一个给定对象。

unique_ptr使用如下的方式创建,它没有make_shared函数

1
unique_ptr<int> up(new int(42));

unique_ptr不支持拷贝,也不支持赋值,如下的操作都是错误的

1
2
3
4
unique_ptr<int> up(new int(42)); //correct
unique_ptr<int> up1(up); // error
unique_ptr<int> up2;
up2 = up; // error

unique_ptr支持的操作

1
2
3
4
5
6
7
8
unique_ptr<T> u1            // 空的unique_ptr,指向类型为T的对象
unique_ptr<T, D> u2 // 定义了Delete函数
unique_ptr<T, D> u(d) // 用d代替delete
u = nullptr // 释放u的对象,将u置空
u.release() // u放弃对指针的控制权,返回指针,并将u置空
u.reset() // 释放u指向的对象
u.reset(q) // 如果提供了内置指针q,则令u指向这个对象
u.reset(nullptr)

虽然unique_ptr不能拷贝,但是可以通过release和reset操作将指针的所有权从一个unique_ptr转移给另一个unique_ptr

1
2
3
unique_ptr<string> p2(p1.release()); // 所有权从p1转移到p2
unique_ptr<string> p3(new string("hello"));
p2.reset(p3.release()); // 所有权从p3转移到p2

案例分析

有一个文本查询程序,输入一个文本,以及一个单词,能够输出这个单词所在文本的行数,同时升序打印这一行的内容。

首先我们要定义数据结构

  • 需要一个vector来存放整个文本的内容
  • 需要一个map>来存放每个单词所在的行数

下面是我们的代码流程

  • 定义一个TextQuery类,用来解析文本,同时保存文本的详细信息
  • 当查询时,返回查询的结构数据,用类QueryResult来保存
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include "string"
#include "vector"
#include "map"
#include "set"
#include "istream"
#include "sstream"
#include "iostream"
#include "fstream"

using namespace std;

using line_no = vector<string>::size_type;

class QueryResult {
friend ostream& print(ostream&, const QueryResult&);
public:
QueryResult(string s, set<line_no> p, vector<string> f) :
name(s), lines(p), file(f) {}
private:
string name;
set<line_no> lines;
vector<string> file;
};

ostream& print(ostream& os, const QueryResult& qr)
{
os << qr.name << " occur:" << qr.lines.size() << " " << ((qr.lines.size() > 1) ? "times" : "time") << endl;
for (auto num : qr.lines) {
os << "\t(line " << num + 1 << ") " << *(qr.file.begin() + num) << endl;
}
return os;
}

class TextQuery {
public:
TextQuery(istream&);
QueryResult query(const string&) const;
private:
vector<string> file;
map<string, set<line_no>> wm;
};

TextQuery::TextQuery(istream& is)
{
string text;
while (getline(is, text)) {
file.push_back(text);
int n = file.size() - 1;
istringstream line(text);
string word;
while(line >> word) {
wm[word].insert(n);
}
}
}

QueryResult TextQuery::query(const string& name) const
{
static set<line_no> nodata;
auto loc = wm.find(name);
if (loc != wm.end()) {
return QueryResult(name, loc->second, file);
} else {
return QueryResult(name, nodata, file);
}
}

void runQuery(ifstream& f)
{
TextQuery tq(f);
while (true) {
cout << "Enter word to search or q for quit:" << endl;
string s;
if (!(cin >> s) || s == "q") {
break;
}
print(cout, tq.query(s));
}
}

int main(int args, char* argv[]) {
ifstream file(argv[1]);
runQuery(file);
return 0;
}

我们看上面的代码有什么问题?看这里

1
return QueryResult(name, loc->second, file);

这一行创建QueryResult,会导致资源的拷贝操作。为了避免资源的拷贝,可以用指针来解决。如果用指针,那么就要对内存进行管理。智能指针现在可以派上用场了

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include "iostream"
#include "istream"
#include "fstream"
#include "sstream"
#include "memory"
#include "string"
#include "vector"
#include "set"
#include "map"

using namespace std;
using line_no = vector<string>::size_type;

class QueryResult {
friend ostream& print(ostream&, const QueryResult&);
public:
QueryResult(string s, shared_ptr<set<line_no> > p, shared_ptr<vector<string> > f) :
sought(s), lines(p), file(f) {}
private:
string sought;
shared_ptr<set<line_no> > lines;
shared_ptr<vector<string> > file;
};

ostream& print(ostream& os, const QueryResult& qr)
{
os << qr.sought << " occur:" << qr.lines->size() << " " << ((qr.lines->size() > 1) ? "times" : "time") << endl;
for (auto num : *qr.lines) {
os << "\t(line " << num + 1 << ") " << *(qr.file->begin() + num) << endl;
}
return os;
}

class TextQuery {
public:
TextQuery(ifstream&);
QueryResult query(const string&) const;
private:
shared_ptr< vector<string> > file;
map< string, shared_ptr<set<line_no> > > wm;
};

// TextQuery::TextQuery(ifstream& is) : file(new vector<string>)
TextQuery::TextQuery(ifstream& is) : file(make_shared<vector<string> >())
{
string text;
while (getline(is, text)) {
file->push_back(text);
int n = file->size() - 1;
istringstream line(text);
string word;
while (line >> word) {
auto& lines = wm[word];
if (!lines) {
lines.reset(new set<line_no>);
}
lines->insert(n);
}

}
}

QueryResult TextQuery::query(const string& sought) const
{
static shared_ptr<set<line_no> > nodata(new set<line_no>);
auto loc = wm.find(sought);
if (loc == wm.end()) {
return QueryResult(sought, nodata, file);
} else {
return QueryResult(sought, loc->second, file);
}
}

void runQuery(ifstream& f)
{
TextQuery tq(f);
while (true) {
cout << "Enter word to search or q for quit:" << endl;
string s;
if (!(cin >> s) || s == "q") {
break;
}
print(cout, tq.query(s));
}
}

int main(int args, char* argv[]) {
ifstream file(argv[1]);
runQuery(file);
return 0;
}
显示 Gitment 评论