智能指针
C++11中智能指针的分类。是线程安全的嘛,如果不是使用时怎么处理。
在C++11中,提供了四种类型的智能指针:
std::unique_ptr: 独特所有权,不能被复制,但可以通过std::move进行所有权转移。std::shared_ptr: 共享所有权,可以有多个shared_ptr指向同一个对象,shared_ptr使用引用计数来跟踪有多少个智能指针指向同一个资源,当最后一个shared_ptr被销毁时,资源也将被释放。std::weak_ptr: 弱引用,它可以指向一个由shared_ptr管理的对象,但它不参与引用计数,主要用来解决shared_ptr可能会引发的循环引用问题。std::auto_ptr: 这是一个被废弃的智能指针,它试图实现独占所有权,但其语义在C++标准中并不怎么清晰,因此在C++11中被std::unique_ptr所取代。
线程安全
关于线程安全性,除std::shared_ptr外,其他智能指针都不是线程安全的。注意,std::shared_ptr的线程安全性仅限于你可以在不同的线程中安全地使用单个shared_ptr的副本。然而,让多个线程同时访问同一shared_ptr实例(例如,一个线程读取,另一个线程写入)可能会导致数据竞争和未定义的行为。因此,如果在多线程环境中使用智能指针,你需要自己进行适当的同步。
以下是一个示例,展示如何在多线程环境中使用std::mutex来同步对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
#include <memory>
#include <mutex>
#include <thread>
std::shared_ptr<int> p;
std::mutex mtx;
void thread_func() {
std::lock_guard<std::mutex> lock(mtx);
// 在这里安全地使用p
}
int main() {
p = std::make_shared<int>(42);
std::thread t1(thread_func);
std::thread t2(thread_func);
t1.join();
t2.join();
return 0;
}
在这个示例中,我们使用了std::mutex和std::lock_guard来保证在多个线程中对p的访问是互斥的,这样就可以避免数据竞争和未定义的行为。
std::shared_ptr的线程安全性仅限于可以在不同的线程中安全地使用单个shared_ptr的副本,正确使用的示例
以下是一个示例,展示std::shared_ptr线程安全性的正确使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <memory>
#include <thread>
void thread_func(std::shared_ptr<int> p) {
// 在这里安全地使用p
}
int main() {
std::shared_ptr<int> p = std::make_shared<int>(42);
// 创建新的shared_ptr副本,并在新线程中使用它们
std::thread t1(thread_func, p);
std::thread t2(thread_func, p);
t1.join();
t2.join();
return 0;
}
在这个示例中,我们在主线程中创建了一个shared_ptr(p),然后在创建新线程时,我们将p传递给thread_func。每次thread_func被调用时,都会创建一个新的shared_ptr副本(即函数参数p),这个副本与原始的shared_ptr(主线程中的p)指向同一个对象。由于std::shared_ptr的线程安全性,这种用法是安全的,每个线程都有自己的shared_ptr副本,且不会发生数据竞争。
share 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
#include <mutex> template <typename T> class shared_ptr { public: // 构造函数 shared_ptr() : ptr_(nullptr), ref_count_(nullptr) {} shared_ptr(T *ptr) : ptr_(ptr), ref_count_(new int(1)) {} shared_ptr(const shared_ptr<T> &other) : ptr_(other.ptr_), ref_count_(other.ref_count_) { if (ref_count_) increment_ref_count(); } // 析构函数 ~shared_ptr() { release(); } // 赋值操作符 shared_ptr<T> &operator=(const shared_ptr<T> &other) { if (this != &other) { release(); ptr_ = other.ptr_; ref_count_ = other.ref_count_; if (ref_count_) increment_ref_count(); } return *this; } // 获取指针 T *get() const { return ptr_; } // 获取引用计数 int use_count() const { std::lock_guard<std::mutex> lock(mutex_); if (ref_count_) return *ref_count_; else return 0; } // 重载解引用操作符 T &operator*() const { return *ptr_; } // 重载箭头操作符 T *operator->() const { return ptr_; } // 重载布尔操作符 operator bool() const { return ptr_ != nullptr; } private: T *ptr_; // 指向被管理的对象 int *ref_count_; // 引用计数 std::mutex mutex_; // 互斥量 // 增加引用计数 void increment_ref_count() { std::lock_guard<std::mutex> lock(mutex_); ++(*ref_count_); } // 释放资源 void release() { std::lock_guard<std::mutex> lock(mutex_); if (ref_count_) { --(*ref_count_); if (*ref_count_ == 0) { delete ptr_; delete ref_count_; } ptr_ = nullptr; ref_count_ = nullptr; } } };
unique ptr简单实现
以下是 std::unique_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
template <typename T>
class unique_ptr {
public:
explicit unique_ptr(T* ptr = nullptr) : ptr_(ptr) {}
~unique_ptr() { delete ptr_; }
// 删除复制构造函数和复制赋值运算符
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
// 提供移动构造函数和移动赋值运算符
unique_ptr(unique_ptr&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
unique_ptr& operator=(unique_ptr&& other) noexcept {
if (this != &other) {
delete ptr_;
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
return *this;
}
T* operator->() { return ptr_; }
T& operator*() { return *ptr_; }
private:
T* ptr_;
};
在上述代码中,当我们尝试复制一个 unique_ptr 时,编译器会报错,因为复制构造函数和复制赋值运算符已被删除。当我们尝试移动一个 unique_ptr 时(例如,将其传递给另一个函数,或从另一个函数返回它),移动构造函数或移动赋值运算符会被调用,它们会从源 unique_ptr 中删除指针,并在目标 unique_ptr 中设置该指针。这就确保了每个 unique_ptr 都有其独占的对象,从而实现了独占所有权。