智能指针

更安全的进行资源管理

概念

  • 存在<memory>头文件中。
  • RAII(Resource Acquisition Is Initialization):是通过借助一个对象的声明周期来控制资源的一种技术。
    • 智能指针就利用了RAII技术。

std::auto_ptr(C++98,慎用)

  • template <class X> class auto_ptr;

  • 管理权限转移,拷贝时,会把被拷贝的对象的资源的管理权限转移给拷贝对象,导致被拷贝对象悬空,访问就会出现问题。

std::unique_ptr(C++11)

  • template <class T, class D = default_delete<T>> class unique_ptr;

  • 不允许拷贝构造,也不允许拷贝赋值运算符

  • 独占所有权,适用于确保对象只有一个所有者的场景。

**std::make_unique**:

  • 用于创建 std::unique_ptr 它简化了 std::unique_ptr 的创建,并提供了与之类似的性能优势。
  • 例如: auto ptr = std::make_unique<MyStruct>(10, 3.14);
  • 需要注意的是,std::make_unique 是 C++14 引入的,在 C++11 中不可用。

std::shared_ptr(C++11)

  • template <class T> class shared_ptr;
  • 支持拷贝赋值和允许拷贝赋值运算符。
  • 但是存在循环引用问题
  • 共享所有权,适用于多个指针需要共享同一个对象的场景。

**std::allocate_sharedstd::make_shared**:

  • std::make_shared是一个用于创建 std::shared_ptr 智能指针的便利函数。

  • std::make_shared 类似,但std::allocate_shared允许使用自定义的分配器来分配内存。

  • 例如: std::shared_ptr<MyStruct> ptr = std::allocate_shared<MyStruct>(my_allocator, 10, 3.14);

操作

  • 使用get()获取原始指针:当需要传递智能指针管理的对象给只接受原始指针的函数时,可以使用get()成员函数。

  • 使用reset()重置智能指针:reset()函数用于改变智能指针所指向的对象。如果智能指针是唯一指向该对象的指针,则原对象会被释放。否则,引用计数会减一。

  • 使用use_count()成员函数来获取当前shared_ptr的引用计数。

  • 使用unique()成员函数来检查shared_ptr是否唯一指向其对象(即引用计数为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
#include <iostream>
#include <memory>
using namespace std;

template <class T>
struct FreeFunc
{
void operator()(T *ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
template <class T>
struct DeleteArrayFunc
{
void operator()(T *ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
int main()
{
FreeFunc<int> freeFunc;
std::shared_ptr<int> sp1((int *)malloc(4), freeFunc);
DeleteArrayFunc<int> deleteArrayFunc;
std::shared_ptr<int> sp2((int *)malloc(4), deleteArrayFunc);

std::shared_ptr<FILE> sp5(fopen("test.txt", "w"), [](FILE *p)
{ fclose(p); });

return 0;
}


循环引用问题

例如两个shared_ptr对象通过两个指针prev,next相互连接,它们相互为对方提供一个引用计数,导致双方的引用计数始终不为0,从而导致循环引用

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
#include <iostream>
#include <utility>
#include <string>

using namespace std;

template <class T>
class SmartPtr
{
public:
// 构造函数
explicit SmartPtr(T *ptr)
: _ptr(ptr), _counter(new int(1))
{
}

// 析构函数
~SmartPtr()
{
if (--*(_counter) == 0)
{
cout << "free" << endl;
delete _ptr;
delete _counter;
}
}

SmartPtr(const SmartPtr<T> &smartPtr)
: _ptr(smartPtr._ptr),
_counter(smartPtr._counter)
{
cout << "拷贝构造"<<endl;
++(*_counter);
}

// 赋值拷貝运算符
SmartPtr<T> &operator=(SmartPtr &other)
{
if (_ptr != other._ptr)
{
if (--*(_counter) == 0)
{
cout << "赋值拷贝中的清0" << endl;
delete _ptr;
delete _counter;
}
cout << "赋值拷贝" << endl;
_counter = other._counter;
_ptr = other._ptr;
++(*_counter);
}
return *this;
}

// 禁用拷贝构造函数和拷贝赋值运算符
// SmartPtr(const SmartPtr& other) = delete;
// SmartPtr& operator=(const SmartPtr& other) = delete;

T *input_ptr()
{
return _ptr;
}
T *output_ptr()
{
return _ptr;
}
T &operator*()
{
return *_ptr;
}
T *&operator->()
{
return _ptr;
}

private:
T *_ptr;
int *_counter;
};

int main()
{
SmartPtr smartPtr_pair(new pair<string, string>("first", "second"));
SmartPtr smartPtr_int(new int(5));

SmartPtr smartPtr_str1(new string("abcd"));
SmartPtr smartPtr_str2(smartPtr_str1);

SmartPtr smartPtr_double1(new double(3.14));
SmartPtr smartPtr_double2(new double(3.15));
smartPtr_double1 = smartPtr_double2;

cout << smartPtr_pair->first << " " << smartPtr_pair->second << endl; // 省略了一个箭头
cout << *smartPtr_int << endl;
/*输出 first second
5
free
free */
return 0;
}


  • pair有省略一个箭头的情况。

std::weak_ptr

  • template class weak_ptr;

  • 不是使用RAII的智能指针,而是专门用来解决循环引用问题的指针。

    • 不增加引用计数,也不参与资源的管理,但是可以访问资源。
  • 它通常与 std::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
#include <iostream>
#include <memory>
#include <vector>

class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev;

Node() { std::cout << "Node created\n"; }
~Node() { std::cout << "Node destroyed\n"; }
};

int main() {
// 创建两个 std::shared_ptr
std::shared_ptr<Node> first = std::make_shared<Node>();
std::shared_ptr<Node> second = std::make_shared<Node>();

// 建立双向链表关系
first->next = second;
second->prev = first;

// 打印使用情况
std::cout << "first use count: " << first.use_count() << std::endl;
std::cout << "second use count: " << second.use_count() << std::endl;

// 当 first 和 second 作用域结束时,Node 对象将被销毁
return 0;
}
  • std::shared_ptr<Node> next: 这个智能指针持有对下一个节点的共享所有权。它会增加 Node 对象的引用计数,当计数为零时对象会被销毁。

  • std::weak_ptr<Node> prev: 它只引用前一个节点,但不会增加它的引用计数。这样,即使当前节点被销毁,前一个节点的资源也不会因为循环引用而无法释放。

资源控制器的简单模拟

  • 使用一个可调用对象来对智能指针申请到的资源进行管理。
    • 如下代码使用包装器来让析构函数正确的调用我们指定的析构方法。
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
#include <iostream>
#include <utility>
#include <string>
#include <functional>

using namespace std;

template <class T>
class SmartPtr
{
public:
template <class D>
SmartPtr(T *ptr,D del)
: _ptr(ptr), _counter(new int(1)),
_del(del)
{
}
// 析构函数
~SmartPtr()
{
if (--*(_counter) == 0)
{
cout << "free" << endl;
//delete _ptr;
_del(_ptr);
delete _counter;
}
}

private:
T *_ptr;
int *_counter;
function<void(T*)> _del;
};

int main()
{

SmartPtr<string> smartPtr_str(new string("abcd"),[](string* ptr){delete(ptr);cout<<"资源管理器";});
/*输出:free
资源管理器*/
return 0;
}


智能指针
https://weihehe.top/2024/08/09/智能指针/
作者
weihehe
发布于
2024年8月9日
许可协议