在C++中,我们通过new(在动态内存中为对象分配空间并初始化对象)和delete(销毁该对象,并释放内存)直接分配和释放动态内存。
如下代码:
1 | int *pi = new int ; //pi 指向一个未初始化的int |
1 | 1 |
有些人有这样的疑问,指针一定要new吗?其实指针和new没有什么关系。这里的new在动态内存里为对象分配了内存空间,并返回了一个指向该对象的指针。new是申请堆空间,不是在栈上分配内存,指针只要指向有效的内存空间就可以。比如:
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 1
2
3
4
5
6
7
8 int
9 i;
10
11
12
13
14
15 int
16 *p = &i;
17
18
19
20
21
22 //p可以直接使用了
23
24
25
26
27
new直接初始化对
象:
1 2 | int *pi = new int (128); //pi指向值为128的对象 string *ps = new string( "christian" ); //*ps 指向“christian”的字符串 |
1 | 1 |
new分配const对象必须进行初始化,并且返回的是一个指向const对象的指针:
1 | const int *p = new const int (1024); //分配并初始化一个const int; |
1 | 1 |
当然new申请内存分配的时候也不是都会成功的,一旦一个程序用光了他的所有可用内存(虽然这种情况一般很少发生),new表达式就会失败。这时候会抛出bad_alloc异常。所以我们需要通过delete来释放占用的内存。在这里注意delete并不是要删除指针,而是释放指针所指的内存。
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 1
2
3
4
5
6
7
8 int
9 i;
10
11
12
13
14
15 int
16 *pi = &i;
17
18
19
20
21
22 string str =
23 "dwarves"
24 ;
25
26
27
28
29
30 double
31 *pd =
32 new
33 double
34 (33);
35
36
37
38
39
40 delete
41 str;
42 // 错误:str不是一个指针
43
44
45
46
47
48 delete
49 pi;
50 // 错误:pi指向一个局部变量
51
52
53
54
55
56 delete
57 pd;
58 // 正确
59
60
61
62
63
使用new和delete来管理动态内存常出的一些错误:
1.忘记delete,即导致了“内存泄漏”,
2.野指针。在对象已经被释放掉之后,(这里注意,此时的指针成为了悬垂指针,即指向曾经存在的对象,但该对象已经不再存在。结果未定义,而且难以检测。)这时候我们再次使用,会产生使用非法内存的指针。
不过如果我们需要保留指针,可以在delete以后将nullptr赋予指针,这样指针就不指向任何对象了,如下代码:
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 1
2
3
4
5
6
7
8 auto p(
9 new
10 auto 42);
11
12
13
14
15
16 auto q = p;
17
18
19
20
21
22 delete
23 p;
24
25
26
27
28
29 p = nullptr;
30
31
32
33
34
题外话:在测试这个问题的时候,我输出了下q的值发现还是42,并且没有报错,后来在delete p之后,我又给*p = 19;这个时候 p ,q的值在输出的时候都是19,也没有报错。这个代码其实根本就是错误的了,因为p,q已经没有有效的内存空间了。这里是释放了内存,但指针的值不变,指向的内存不会清0,指向的这片内存区域是待分配的,如果没有被其他数据覆盖的话,你就能幸运得输出这主要原因是你分配的内存小,没有继续分配,被占用的概率小所致。我用的xcode,换到VS下就正常报错了,是因为VS为了从编译器的角度上解决缓冲区溢出等问题,加上的这种功能,C++标准里面没有这么要求,所有xcode和gcc是不会检查的。所以在这里 建议大家写纯C++代码的时候用vs。
3.重复delete,就会使自由空间遭到破坏如:
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 1
2
3
4
5
6
7
8 string *ps1 =
9 new
10 string (
11 "one"
12 ),*ps2 = ps1;
13
14
15
16
17
18 delete
19 ps1;
20
21
22
23
24
25 delete
26 ps2;
27 //ps2的内存已经被释放了
28
29
30
31
32
虽然显示的管理内存在性能上有一定的优势,但是随着多线程程序的出现和广泛使用,内存管理不佳的的情况变得更严重。所以C++标准库中的智能指针很好的解决了这些问题。
auto_ptr以对象的方式管理堆分配的内存,并在适当的时间(比如析构),释放内存。我们只需要将new操作返回的指针作为auto_ptr的初始值,而不需要调用delete:
1 | auto_ptr ( new int ); |
1 | 1 |
但是auto_ptr在拷贝时会返回一个左值并且不能调用delete[];所以在C++11中改用shared_ptr(允许多个指针指向一个对象),unique_ptr(“独占”所指向的对象)还有weak_ptr它是一种不控制所指对象生存期的智能指针,指向shared_ptr所管理的对像,在memory头文件中。
1 | shared_ptr |
1 | 1 |
如下代码:
1 | shared_ptr< int > pi; //指向int |
1 | 1 |
当然我们也可以shared_ptr和new来结合使用,但是必须使用直接初始化的形式来初始化一个智能指针,
1 2 | shared_ptr< int > p1 = new int (1024); //error:必须使用直接初始化的形式 shared_ptr< int > p2( new int (1024)); |
1 | 1 |
但是最好不要混合使用普通指针和智能指针,最安全的分配和使用动态内存的方法是调用make_shared的标准库函数。在使用它的时候,必须指定想要创建的对象类型。
1 2 | shared_ptr< int >pi = make_shared< int >(1); //指向一个值为1的int的shared_ptr shared_ptr<string>ps = make_shared<string>(10, 'a' ); //ps为指向“aaaaaaaaaa”的string |
1 | 1 |
如果我们不传递任何参数,对象会进行值初始化
1 | shared_ptr< int >pi = make_shared< int >(); //初始化默认值为0; |
1 | 1 |
shared_ptr 实现了引用计数型的智能指针,当进行拷贝的时候,计数器都会递增。而对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(减1,如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数(加1),如下代码:
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 1
2
3
4
5
6
7
8 auto p = make_shared<
9 int
10 >(13);
11 //p 指向的对象只有p一个引用者
12
13
14
15
16
17 auto q(p);
18 //此时对象有两个引用者
19
20
21
22
23
24 auto r = make_shared<
25 int
26 >(10);
27
28
29
30
31
32 r = q;
33
34
35
36
37
此时r的引用技术为0,r原指对象被自动释放。q的引用计数增加。
weak_ptr的使用和析构都不会改变shared_ptr的引用计数。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的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
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171 1
2
3
4
5
6
7
8 #
9 include
10 <boost/smart_ptr.hpp>
11
12
13
14
15
16 #
17 include
18 <boost/make_shared.hpp>
19
20
21
22
23
24 using
25 namespace
26 boost;
27
28
29
30
31
32 using
33 namespace
34 std;
35
36
37
38
39
40 int
41 _tmain(
42 int
43 argc, _TCHAR* argv[])
44
45
46
47
48
49 {
50
51
52
53
54
55
56 shared_ptr<
57 int
58 > sp(
59 new
60 int
61 (
62 10
63 ));
64
65
66
67
68
69
70 assert(sp.use_count() ==
71 1
72 );
73
74
75
76
77
78
79 weak_ptr<
80 int
81 > wp(sp);
82 //从shared_ptr创建weak_ptr
83
84
85
86
87
88
89 assert(wp.use_count() ==
90 1
91 );
92
93
94
95
96
97
98 if
99 (!wp.expired())
100 //判断weak_ptr观察的对象是否失效
101
102
103
104
105
106
107 {
108
109
110
111
112
113
114 shared_ptr<
115 int
116 > sp2 = wp.lock();
117 //获得一个shared_ptr
118
119
120
121
122
123
124 *sp2 =
125 100
126 ;
127
128
129
130
131
132
133 assert(wp.use_count() ==
134 2
135 );
136
137
138
139
140
141
142 }
143
144
145
146
147
148
149 assert(wp.use_count() ==
150 1
151 );
152
153
154
155
156
157
158 return
159 0
160 ;
161
162
163
164
165
166 }
167
168
169
170
171
当我们定义一个unique_ptr的时候,需要将其绑定到一个new返回的指针。
只能有一个uniqu_ptr指向对象,也就是说它不能被拷贝,也不支持赋值。但是我们可以通过move来移动
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 1
2
3
4
5
6
7
8 std::unique_ptr<
9 int
10 > p1(
11 new
12 int
13 (5));
14
15
16
17
18
19 std::unique_ptr<
20 int
21 > p2 = p1;
22 // 编译会出错
23
24
25
26
27
28 std::unique_ptr<
29 int
30 > p3 = std::move(p1);
31 // 转移所有权, 现在那块内存归p3所有, p1成为无效的指针.
32
33
34
35
36
37
38
39
40
41
42
43 p3.reset();
44 //释放内存.
45
46
47
48
49
50 p1.reset();
51 //实际上什么都没做.
52
53
54
55
56