智能指针是C++中的一种对象,它像常规指针一样可以指向动态分配的内存,但在不再需要时可以自动释放该内存。这可以防止因忘记调用delete
而导致的内存泄漏。C++标准库提供了三种类型的智能指针:std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
.
-
unique_ptr:唯一拥有其所指对象,没有引用计数。
-
shared_ptr:允许多个智能指针共享同一个对象,使用引用计数来确保当最后一个对该对象的引用消失时,该对象会被正确地销毁。
-
weak_ptr:是为了配合
shared_ptr
而提供的一种弱引用机制;它对所管理的对象不具有所有权。
简单实现三种智能指针:
一:unique_ptr
template <typename T>
class unique_ptr
{
public:
explicit unique_ptr(T* ptr = nullptr) : m_ptr(ptr) {}
~unique_ptr() { delete m_ptr; }
//移动构造函数 和 移动赋值运算符
unique_ptr(unique_ptr&& other) : m_ptr(other.release()) {}
unique_ptr& operator=(unique_ptr&& other)
{
reset(other.release());
return *this;
}
T& operator*() const { return *m_ptr; }
T* operator->() const { return m_ptr; }
//删除拷贝语义
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
T* release()
{
T* ptr = m_ptr;
m_ptr = nullptr;
return ptr;
}
void reset(T* ptr = nullptr)
{
delete m_ptr;
m_ptr = ptr;
}
private:
T* m_ptr;
};
二:shared_ptr
struct ControlBlock
{
size_t strong_count;
size_t weak_count;
//考虑线程安全 增加:fetch_add(num) 减少:fetch_sub(num) 读取数据 load() 赋值 store(num)
//std::atomic<size_t> strong_count;
//std::atomic<size_t> weak_count;
};
template <typename T>
class shared_ptr {
public:
shared_ptr() :m_ptr(nullptr), m_ctrl(nullptr) {}
explicit shared_ptr(T* ptr = nullptr) : m_ptr(ptr), m_ctrl(ptr ? new ControlBlock{ 1, 0 } : nullptr) {}
explicit shared_ptr(T* ptr, ControlBlock& controlBlock) :m_ptr(ptr), m_ctrl(controlBlock) {}
shared_ptr(const weak_ptr<T>& weakptr) :m_ptr(weakptr.m_ptr), m_ctrl(weakptr.m_ctrl)
{
if (m_ctrl) m_ctrl->strong_count++;
}
~shared_ptr() { decrease_and_destroy(); }
shared_ptr(const shared_ptr& other) : m_ptr(other.m_ptr), m_ctrl(other.m_ctrl)
{
if (m_ptr) ++m_ctrl->strong_count;
}
shared_ptr(shared_ptr&& other) noexcept :m_ptr(other.m_ptr), m_ctrl(other.m_ctrl)
{
other.m_ptr = nullptr;
other.m_ctrl = nullptr;
}
shared_ptr& operator=(const shared_ptr& other)
{
if (&other != this)
{
if (other.m_ptr) ++other.m_ctrl->strong_count;
decrease_and_destroy();
m_ptr = other.m_ptr;
m_ctrl = other.m_ctrl;
}
return *this;
}
shared_ptr& operator=(shared_ptr&& other) noexcept
{
if (&other != this)
{
decrease_and_destroy();
m_ptr = other.m_ptr;
m_ctrl = other.m_ctrl;
other.m_ctrl = nullptr;
other.m_ptr = nullptr;
}
return *this;
}
static shared_ptr make_shared(T* ptr = nullptr)
{
//将指针和控制块分配到一块内存中
struct ControlBlockWithObject : ControlBlock
{
T object;
};
auto* ctrl = new ControlBlockWithObject;
ctrl->strong_count = 1;
ctrl->weak_count = 0;
return shared_ptr(&ctrl->object, ctrl);
}
T& operator*() const { return *m_ptr; }
T* operator->() const { return m_ptr; }
private:
T* m_ptr;
ControlBlock* m_ctrl;
void decrease_and_destroy()
{
if (m_ptr && --m_ctrl->strong_count == 0)
{
delete m_ptr;
if (m_ctrl->weak_count == 0) delete m_ctrl;
}
}
};
三:weak_ptr
template <typename T>
class weak_ptr {
public:
weak_ptr() : m_ptr(nullptr), m_ctrl(nullptr) {}
weak_ptr(const shared_ptr<T>& spt) : m_ptr(spt.m_ptr), m_ctrl(spt.m_ctrl)
{
if (m_ctrl) ++m_ctrl->weak_count;
}
~weak_ptr() { decrease(); }
weak_ptr(const weak_ptr& other) : m_ptr(other.m_ptr), m_ctrl(other.m_ctrl)
{
if (m_ctrl) ++m_ctrl->weak_count;
}
weak_ptr& operator=(const weak_ptr& other)
{
if (&other != this)
{
if (m_ctrl) ++m_ctrl->weak_count;
decrease();
m_ptr = other.m_ptr;
m_ctrl = other.m_ctrl;
}
return *this;
}
shared_ptr<T> lock() const
{
if (m_ctrl && m_ctrl->strong_count > 0) return shared_ptr<T>(*this);
else return shared_ptr<T>();
}
private:
T* m_ptr;
ControlBlock* m_ctrl;
void decrease()
{
if (m_ctrl && --m_ctrl->weak_count == 0 && m_ctrl->strong_count == 0) delete m_ctrl;
}
};
对于 std::shared_ptr
, 它内部实现了一个控制块,这个控制块包含了两个计数器:
- 一个是 shared 引用计数
- 另一个是 weak 引用计数
当我们创建一个新的 shared_ptr
或者复制现有的 shared_ptr
, shared 引用计数会增加。当 shared_ptr
被销毁或者超出作用域, shared 计数器就会减少。只有当 shared 计数器为0时, 才会删除所管理资源(即调用 delete)。
同时,每次创建或复制 weak_ptr
, weak 引用计数就增加; 每次销毁或超出作用域, weak 计数器就减少。只有当两个计数器都为0时, 才会删除控制块本身。
std::shared_ptr
的引用计数通常存储在动态分配的控制块中。这个控制块是在创建std::shared_ptr
实例时由标准库自动分配的。这个控制块不仅包含引用计数,还可能包含被管理对象的析构函数和其他一些必要信息。
std::shared_ptr
和std::weak_ptr
都是C++11引入的智能指针类型,它们共享同一个控制块,这个控制块中包含了被管理对象的析构函数、共享引用计数(shared reference count)和弱引用计数(weak reference count)。
-
共享引用计数:记录了有多少个
std::shared_ptr
实例正在指向同一个对象。当最后一个std::shared_ptr
被销毁或者超出作用域时(即共享引用计数变为0),那么被管理的对象就会被自动删除。 -
弱引用计数:记录了有多少个
std::weak_ptr
实例正在指向同一个对象。不过与std::shared_ptr
不同,std::weak_ptr
不会延长目标对象的生命周期。也就是说,即使还有活跃的std::weak_ptr
, 目标对象也可能已经被销毁。
为什么需要两种不同类型的引用计数呢?
原因在于防止循环引用导致内存泄漏,并且让弱指针可以安全地访问资源。如果只有共享引用计数而没有弱引用计数,在一些情况下可能会导致内存泄漏。例如,在某些数据结构(如双向链表或图)中,节点之间可能形成循环依赖关系,这样每个节点都无法被释放。
此外,当我们需要创建一个不拥有资源但需要观察资源状态的智能指针时(比如检查资源是否还存在),我们可以使用 std::weak_ptr。在转换 weak_ptr 为 shared_ptr 时, 如果 shared 引用计数已经为0 (意味着资源已经释放), 那么得到的 shared_ptr 将为空 (null), 这样就避免了悬挂指针问题。