C++智能指针

为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。智能指针的行为类似常规指针,最重要的区别是它负责自动释放所指向的对象。不需要考虑内存泄露。

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

unique_ptr则独占所指向的对象;

标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。

1. unique_ptr

一个unique_ptr拥有它所指向的对象。

当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上,初始化unique_ptr必须采用直接初始化形式。

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <memory>
using namespace std;

int main(){
unique_ptr<int> unPtr1(new int(10));
cout << *unPtr1 << endl;
return 0;
}

输出结果:

10

unique_ptr不支持普通的拷贝或赋值操作。

1
2
3
unique_ptr<int> unPtr1(new int(10));
unique_ptr<int> unPtr2(unPtr1); //error: unique_ptr 不支持拷贝
unique_ptr<int> unPtr2 = unPtr1; //error: unique_ptr 不支持赋值

虽然我们不能拷贝或赋值unique_ptr,但可以通过调用move来转移所有权。

1
2
3
unique_ptr<int> unPtr1(new int(10));
unique_ptr<int> unPtr2 = move(unPtr1);
cout << "unptr2 = " << *unPtr2 << endl;

输出结果:

unptr2 = 10

需要注意的是,一旦你转移了指针的所有权,之前的所有者,也就是unPtr1就变为了空指针。

C++ 11标准库中默认实现了make_shared,但是没有给出一个make_unique的实现。 本例实现make_unique

技术要点:

  1. 使用模板函数重载,分别支持普通指针,变长数组,不支持定长数组
  2. std::enable_if关键字根据不同条件,调用不同模板
  3. std::unique_ptr能构造和析构数组

make_unique.h

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
#ifndef _MAKE_UNIQUE_HPP_
#define _MAKE_UNIQUE_HPP_
#include <type_traits>
#include <memory>

// 单一元素类模板定义
template <typename T>
using Ele = typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T> >::type;

// 变长数组类模板定义
template <typename T>
using Slice = typename std::enable_if<std::is_array<T>::value && std::extent<T>::value == 0, std::unique_ptr<T>>::type;

// 定长数组类模板定义
template <typename T>
using Arr = typename std::enable_if<std::extent<T>::value != 0, void>::type;

// 支持普通指针
template <typename T, typename ... Args> inline
Ele<T> make_unique(Args && ... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

// 支持动态数组
template <typename T> inline
Slice<T> make_unique(size_t size) {
using U = typename std::remove_extent<T>::type;
return std::unique_ptr<T>(new U[size]);
}

// 过滤定长数组
template <typename T, typename ... Args>
Arr<T> make_unique(Args &&...) = delete;

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <memory>
#include "make_unique.h"
using namespace std;

class myClass{
public:
myClass(){
cout << "Constructor invoked \n";
}
~myClass(){
cout << "Destructor invoked \n";
}
};

int main(){
unique_ptr<myClass> unPtr1 = make_unique<myClass>();
return 0;
}

输出结果:

Constructor invoked

Destructor invoked

2. shared_ptr

共享指针可以被共享,可以被多个所有者共享。智能指针也是模版,当我们创建一个智能指针时,必须提供额外的信息------指针可以指向的类型。

2.1 如何创建shared_ptr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <memory>
using namespace std;

class myClass{
public:
myClass(){
cout << "Constructor invoked \n";
}
~myClass(){
cout << "Destructor invoked \n";
}
};

int main(){
shared_ptr<myClass> shPtr1 = make_shared<myClass>();
return 0;
}

共享指针有所有指向这个指针的引用次数。我们可以通过use_count方法来查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <memory>
using namespace std;

class myClass{
public:
myClass(){
cout << "Constructor invoked \n";
}
~myClass(){
cout << "Destructor invoked \n";
}
};

int main(){
shared_ptr<myClass> shPtr1 = make_shared<myClass>();
cout << "Shared count: " << shPtr1.use_count() << endl;
return 0;
}

输出结果为:

Constructor invoked

Shared count: 1

Destructor invoked

可以看出我们只有一个指向这个类的指针。所以结果为1,我们再创建一个类共享指针。

1
2
3
4
shared_ptr<myClass> shPtr1 = make_shared<myClass>();
cout << "Shared count: " << shPtr1.use_count() << endl;
shared_ptr<myClass> shPtr2 = shPtr1;
cout << "Shared count: " << shPtr1.use_count() << endl;

输出结果:

Constructor invoked

Shared count: 1

Shared count: 2

Destructor invoked

可以看出此时结果为2。当我们销毁一个指向这个类的指针时,引用次数减一,直到引用次数为0,内存才会被释放。

1
2
3
4
5
6
7
shared_ptr<myClass> shPtr1 = make_shared<myClass>();
cout << "Shared count: " << shPtr1.use_count() << endl;
{
shared_ptr<myClass> shPtr2 = shPtr1;
cout << "Shared count: " << shPtr1.use_count() << endl;
}
cout << "Shared count: " << shPtr1.use_count() << endl;

输出结果为:

Shared count: 1

Shared count: 2

Shared count: 1

可以看到shPtr2超出它的作用域之后就被销毁了,所以最后的引用次数为1

3. weak_ptr

弱引用weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。前面我们知道多个shared_ptr指向一个特定的内存位置时,会导致引用次数的增加。而将一个weak_ptr绑定到一个shared_ptr不会改变share_ptr的引用次数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使weak_ptr指向对象,对象也会被释。

1
2
3
4
5
weak_ptr<int> wePtr1;
{
shared_ptr<int> shPtr1 = make_shared<int>(25);
wePtr1 = shPtr1;
}

weptr1就被赋值为25