C++智能指针
目录
- 1. 简要介绍
- 2. 为什么要使用智能指针(普通指针的不足)
- 3. 智能指针的优势
- 4. unique_ptr使用注意事项
- 5. 引用计数的致命问题
- 6. 智能指针是线程安全的吗
- 7. std::move()函数在什么情况下会失效
- 7. 实现一个自己的shared_ptr
- 8. 实现一个自己的unique_ptr
- 参考链接:
1. 简要介绍
智能指针是为了更加安全的使用动态内存,与常规指针的区别是可以自动释放所指向的对象。是RAII
思想的一个很好的例子。
智能指针是类模板,把普通指针交给智能指针对象来进行管理。
2. 为什么要使用智能指针(普通指针的不足)
为了更加安全的使用动态内存
- new和new[]的内存需要使用delete和delete[]来进行释放
- 程序员可能失误忘记释放内存
- 程序员也不确定何时释放内存,例如在多线程问题中
3. 智能指针的优势
为了更安全的使用堆区的动态内存,避免出现内存泄漏的问题,c++中引入了多种智能指针。智能指针是一个类模板,可以把普通指针交给智能指针对象来进行管理。在超出指针作用域时,智能指针会自动调用指针的析构函数来释放资源,避免了内存泄漏的问题。
unique_ptr
所有的智能指针都在 #include<memory>
头文件中
unique_ptr
独占其指向的对象,也就是说,同时只有一个unique_ptr
指向同一个对象,当这个**unique_ptr
**被销毁的时候,指向的对象也被销毁
4. **unique_ptr
**使用注意事项
下面这些是在使用**shared_ptr
**的时候也要注意的
- 不要用一个裸指针初始化多个
unique_ptr
对象,避免多次调用析构函数,出现野指针的情况 - 不支持(+、-、++、—)等运算符操作
unique_ptr
参数传递的时候,可以传引用(因为拷贝构造函数被禁用了),也可以调用get()
函数传递裸指针
unique_ptr
不是绝对安全
shared_ptr
shared_ptr
**共享它指向的对象,多个shared_ptr
可以关联相同的对象,在内部采用引用计数机制**来实现。
shared_ptr
的构造函数也是explicit,但是没有删除拷贝构造函数和赋值函数。(与unique_ptr
的不同)
另外的不同点就是,在c++11版本中,就提供了make_shared
函数
如果unique_ptr
能解决问题,就不要使用shared_ptr
。unique_ptr
的效率更高,占用的资源更少。
shared_ptr
可能存在的问题
shared_ptr
内部维护了一个共享的引用计数器,多个shared_ptr
可以指向同一个资源
如果出现了循环引用的情况,引用计数永远无法归0,资源不会被释放
weak_ptr
weak_ptr
是为了配合shared_ptr
而引入的,它指向一个由shared_ptr
管理的资源但不影响资源生命周期,也就是说,将一个**weak_ptr
绑定到一个shared_ptr
不会改变shared_ptr
**的引用计数。
不论是否由weak_ptr
指向,如果最后一个指向资源的shared_ptr
被销毁,资源就会被释放
5. 引用计数的致命问题
引用计数是计算机编程语言中的一种内存管理技术,当管理对象的引用次数变成0时,就释放资源
Python中使用引用计数的方式来管理内存
01循环引用
C++中的循环引用主要是在智能指针shared_ptr
的使用中导致的,解决方法是使用weak_ptr
指向shared_ptr
管理的资源
6. 智能指针是线程安全的吗
智能指针是线程安全的,但是智能指针管理的资源不是线程安全的,需要自己手动控制
修改引用计数的操作是原子的
规则
- 同一个
shared_ptr
并发读时,是安全的,但是写是不安全的 - 共享引用计数的
shared_ptr
被并发读写时,都是安全的
智能指针写操作:修改原始指针的指向,如swap()
reset()
和析构函数,智能指针中有两个成员,一个原始指针一个引用计数
读操作只是把智能指针赋值给另一个智能指针,这个不会修改原始指针的指向,只会修改引用计数,所以是线程安全的。
写操作有两个事情,先修改原始指针,然后再修改引用计数,这两个步骤是没有加锁保护的,所以两个操作加起来就不是原子的
7. std::move()
函数在什么情况下会失效
move()函数失效意味着没有按照预期调用移动构造函数,而是调用了拷贝构造函数
std::move()是否可以保证一定能移动成功?
答案显然是否定的
A a = std::move(b);
本质上是先将b强制转化了右值引用A&&
, 然后触发了移动构造函数,在移动构造函数中,完成了对象b到对象a的移动。
例如
const std::string str = "123";
std::string str2(std::move(str));
以上,对str
对象调用std::move
,强转出的类型是const string &&
,而不是 string &&
,这样移动构造函数就不会起作用了,但是这个类型可以令复制构造函数生效。
7. 实现一个自己的shared_ptr
以下是**shared_ptr
**的简单实现
#include <iostream>
template <typename T>
class shared_ptr {
public:
// 构造函数,传入一个指针对象
explicit shared_ptr(T* ptr = nullptr) : ptr_(ptr), count_(new size_t(1)) {}
// 拷贝构造函数,拷贝指针和计数器
shared_ptr(const shared_ptr<T>& other) : ptr_(other.ptr_), count_(other.count_) {
// 使用指针是因为
(*count_) ++;
}
// 析构函数,当计数器为0时释放内存
~shared_ptr() {
(*count) --;
if (*count == 0) {
delete ptr_;
ptr_ = nullptr;
delete count_;
count = nullptr;
}
}
// 重载赋值运算符,拷贝指针和计数器
shared_ptr<T>& operator=(const shared_ptr<T>& other) {
if (this != &other) {
// 原来管理的资源引用计数减少,如果减为0,则销毁资源
(*count_) --;
if (*count_ == 0) {
delete ptr_;
ptr_ = nullptr;
delete count_;
count = nullptr;
}
// 新资源的引用计数增加,拷贝指针和计数器
ptr_ = other.ptr_;
count_ = other.count_;
(*count_) ++;
}
return *this;
}
// 重载->操作符,返回指针
T* operator->() const {
return ptr_;
}
// 重载*操作符,返回引用
T& operator*() const {
return *ptr_;
}
// 返回计数器的值
size_t use_count() const {
return *count_;
}
private:
T* ptr_;
size_t* count_;
}
以上是一个简单的shared_ptr
的实现,其中计数器采用动态分配内存的方式,每个shared_ptr
对象的计数器指向同一个内存区域,以确保引用计数的正确性。
当shared_ptr
对象被拷贝时,计数器+1;当shared_ptr
对象被析构时,计数器-1。当计数器的值为0时,释放指针和计数器所指向的内存。
8. 实现一个自己的unique_ptr
需要注意的知识点
- 异常
unique_ptr
本身功能- 三五法则和阻止拷贝
- 隐式的类类型转换
- 移动构造、移动赋值及自赋值问题
- 编写模板类
#include <iostream>
template<typename T>
class MyUniquePtr {
public:
explicit MyUniquePtr(T* ptr = nullptr) : mPtr(ptr) {}
~MyUniquePtr() {
if (mPtr) {
delete mPtr;
mPtr = nullptr;
}
}
// 移动构造函数和移动赋值
MyUniquePtr(MyUniquePtr &&p) noexcept : mPtr(p.mPtr){
p.mPtr = nullptr;
}
MyUniquePtr& operator=(MyUniquePtr &&p) noexcept {
swap(*this, p);
return *this;
}
// 禁用拷贝构造函数和重载
MyUniquePtr(const MyUniquePtr & p) = delete;
MyUniquePtr& operator=(const MyUniquePtr &p) = delete;
T& operator*() const noexcept {
return *mPtr;
}
T* operator->() const noexcept {
return mPtr;
}
explicit operator bool() const noexcept {
return mPtr;
}
// 重新设置unique_ptr管理的资源
void reset(T* q = nullptr) noexcept {
if (q != mPtr) {
if (mPtr)
delete mPtr;
mPtr = q;
}
}
// release() ,释放资源,并返回指针
T* release() noexcept {
T* res = mPtr;
mPtr = nullptr;
return res;
}
T* get() const noexcept {
return mPtr;
}
void swap(MyUniquePtr & p) noexcept {
using std::swap;
swap(mPtr, p.mPtr);
}
private:
T* mPtr;
}
参考链接:
https://cloud.tencent.com/developer/article/1516496