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
#include<iostream>
#include<mutex>
#include<memory>
#include<shared_mutex>
using namespace std;
//写时复制包装器模板类
template<typename T>
class CowPtr
{
private:
mutable shared_ptr<T> ptr; //mutable 关键字允许在 const 方法中修改这个成员变量
mutable shared_mutex mutex;//mutable 关键字允许在 const 方法中使用锁,shared_mutex为可读锁
public:
CowPtr(T* p = nullptr):ptr(p) {}//构造函数
CowPtr(const shared_ptr<T>& p):ptr(p){}//通过shared_ptr构造
CowPtr(const CowPtr& other)
{
lock_guard<shared_mutex>lock(other.mutex);
ptr = other.ptr;
}
CowPtr(CowPtr&& other)noexcept//noexcept用来声明一个函数不会抛出任何异常
{
/*
* 这里的 CowPtr(CowPtr&& other) 表示 接受一个右值引用 类型的 other 参数。它通常用于“移动”资源而不是“复制”资源。
std::move(other.ptr) 将 other.ptr 从一个左值变为右值,并允许移动其内部资源,而不是复制它们
*/
lock_guard<shared_mutex>lock(other.mutex);//确保在移动资源时,不会发生数据竞争
ptr = move(other.ptr);
}
//赋值操作符
CowPtr& operator=(const CowPtr& other)
{
if (this != &other)
{
unique_lock<shared_mutex>lock_this(mutex, defer_lock);
unique_lock<shared_mutex>lock_other(other.mutex, defer_lock);
/*
* std::unique_lock 比 lock_guard 更灵活,允许延迟锁定(std::defer_lock)
std::lock 函数可以同时锁定多个互斥锁,且能避免死锁
*/
lock(lock_this, lock_other);
ptr = other.ptr;
}
return *this;
}
// 移动赋值操作符
CowPtr& operator=(CowPtr&& other) noexcept {
if (this != &other) {
std::unique_lock<shared_mutex> lock_this(mutex, std::defer_lock);
std::unique_lock<shared_mutex> lock_other(other.mutex, std::defer_lock);

std::lock(lock_this, lock_other);

ptr = std::move(other.ptr);
}
return *this;
}

// 析构函数
~CowPtr() = default;

// 读取操作 - 返回常量引用
const T& read() const {
std::lock_guard<std::shared_mutex> lock(mutex);
return *ptr;
}


// 写入操作 - 返回可修改的引用,需要时执行写时复制
T& write() {
std::lock_guard<shared_mutex> lock(mutex);

// 检查引用计数,如果不是唯一拥有者,则复制一份
if (!ptr.unique()) {
ptr = std::make_shared<T>(*ptr);
}

return *ptr;
}
/*
* 写时复制策略,它的核心目的是 保证多个线程能够安全地共享资源,直到其中一个线程需要修改该资源时,才会复制该资源
* 实质上,当有需要写入的操作时,首先判断是否为唯一引用,如果是,那么直接修改不会影响
* 如果不是唯一引用,那么则复制一份数据的副本,原数据与副本各自独立,复制过程中用锁保证不会被其他线程修改
* 然后将副本修改,独自占用,其他读的操作,仍然读取原始数据
*/
};