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

释放双眼,带上耳机,听听看~!

在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&lt;
9        int
10        &gt;(13);
11        //p 指向的对象只有p一个引用者
12      
13
14      
15
16
17        auto q(p);
18        //此时对象有两个引用者
19      
20
21      
22
23
24        auto r = make_shared&lt;
25        int
26        &gt;(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        &lt;boost/smart_ptr.hpp&gt;
11      
12
13      
14
15
16        #
17        include 
18        &lt;boost/make_shared.hpp&gt;
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&lt;
57        int
58        &gt; 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&lt;
80        int
81        &gt; 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&lt;
115        int
116        &gt; 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&lt;
9        int
10        &gt; p1(
11        new 
12        int
13        (5));
14      
15
16      
17
18
19        std::unique_ptr&lt;
20        int
21        &gt; p2 = p1; 
22        // 编译会出错
23      
24
25      
26
27
28        std::unique_ptr&lt;
29        int
30        &gt; 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

给TA打赏
共{{data.count}}人
人已打赏
安全技术

C++ 中 struct和class 的区别

2022-1-11 12:36:11

安全运维

docker搭建私有仓库

2021-12-12 17:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索