c++ 智能指针原理
智能指针是 C++ 中的一种特殊指针,它能够自动管理动态分配的内存资源,避免内存泄漏和悬空指针等问题。智能指针的原理如下:
- RAII(资源获取即初始化)原理:
智能指针利用 RAII 原则,通过在对象构造函数中获取资源(如动态分配的内存空间),并在对象析构函数中释放资源,来确保资源的正确释放。
当智能指针对象离开其作用域时(例如函数结束或对象销毁),其析构函数会被自动调用,从而释放资源。 - 引用计数:
智能指针通常使用引用计数来跟踪其所管理的资源被引用的次数。每当有新的智能指针指向某个资源时,该资源的引用计数加一;当智能指针被销毁时,该资源的引用计数减一。当引用计数为零时,表示资源没有被引用,可以安全释放。
引用计数通常存储在一个独立的计数器中,该计数器与资源相关联。 - 智能指针类型:
C++ 标准库提供了几种智能指针类型,如 std::shared_ptr、std::unique_ptr 和 std::weak_ptr。
std::shared_ptr 使用共享引用计数,允许多个智能指针共享同一资源,并在最后一个指针离开作用域时释放资源。
std::unique_ptr 使用独占式拥有权,每个资源只能被一个 std::unique_ptr 指针所拥有,并在指针销毁时自动释放资源。
std::weak_ptr 是 std::shared_ptr 的弱引用,不会增加资源的引用计数,可以用于避免 std::shared_ptr 的循环引用问题。 - 自定义智能指针:
除了标准库提供的智能指针之外,程序员还可以自定义智能指针,根据自己的需求实现相应的资源管理策略。
自定义智能指针可以通过重载析构函数、复制构造函数和移动构造函数等方式来实现资源管理和所有权转移。
std::shared_ptr原理
- 引用计数:
std::shared_ptr 内部维护了一个引用计数(reference count),用于记录有多少个 std::shared_ptr 共享同一块内存资源。
每当创建一个新的 std::shared_ptr 对象时,引用计数加一;每当销毁一个 std::shared_ptr 对象时,引用计数减一。
当引用计数变为零时,表示没有任何 std::shared_ptr 对象指向该内存资源,此时自动释放该内存资源。 - 内存管理:
std::shared_ptr 对象本身占用的内存资源非常小,通常只包含一个指向被管理对象的指针和一个指向引用计数的指针。
当需要动态分配内存时,可以使用 std::make_shared 函数创建一个 std::shared_ptr 对象,并将其指向新分配的内存空间。
当不再需要这块内存空间时,当 std::shared_ptr 对象超出作用域或被显式销毁时,引用计数减一,如果引用计数变为零,则自动释放内存空间。 - 线程安全:
std::shared_ptr 是线程安全的,多个线程可以同时访问和修改同一个 std::shared_ptr 对象,而不需要额外的同步操作。
这是因为 std::shared_ptr 内部的引用计数是原子操作,可以保证在多线程环境下的正确性。 - 循环引用:
如果存在循环引用(例如 A 对象持有一个指向 B 对象的 std::shared_ptr,而 B 对象也持有一个指向 A 对象的 std::shared_ptr),则可能导致内存泄漏,因为这些对象的引用计数永远不会变为零。
为了避免循环引用导致的内存泄漏,可以使用 std::weak_ptr 来打破循环引用关系。 - 总的来说,std::shared_ptr 是 C++ 中一种方便且安全地管理动态分配内存的方式,它通过引用计数机制实现多个智能指针共享同一块内存资源的所有权,避免了内存泄漏和悬空指针的问题。
std::unique_ptr原理
- 独占所有权:
std::unique_ptr 通过独占所有权的方式管理动态分配的内存资源。一个 std::unique_ptr 对象拥有对其管理的内存资源的唯一所有权。
当一个 std::unique_ptr 对象被销毁时,它所管理的内存资源也会被自动释放。 - 移动语义:
std::unique_ptr 支持移动语义,即可以通过移动操作将所有权从一个 std::unique_ptr 对象转移给另一个对象。
移动操作不会触发内存资源的拷贝,而是将原来的 std::unique_ptr 对象置为空指针,同时将所有权转移到新的对象上。 - 禁止拷贝:
由于 std::unique_ptr 独占所有权的特性,禁止了拷贝构造和拷贝赋值操作,即不能通过拷贝方式创建或赋值一个 std::unique_ptr 对象。
但可以通过移动操作来实现资源的转移。 - 轻量级:
std::unique_ptr 对象本身非常轻量级,通常只包含一个指向被管理对象的指针,因此占用的内存资源很小。 - 自定义删除器:
std::unique_ptr 允许指定自定义的删除器(deleter),用于在释放内存资源时执行特定的清理操作。这使得 std::unique_ptr 对象可以用于管理非堆内存资源,例如文件句柄、套接字等。 - 总的来说,std::unique_ptr 提供了一种安全、高效地管理动态分配内存资源的方式,它通过独占所有权的机制避免了内存泄漏和悬空指针的问题,并支持移动语义和自定义删除器,使得资源管理更加灵活和方便。
std::weak_ptr原理
- 弱引用:
std::weak_ptr 是一种弱引用,它允许共享一个对象的所有权,但不会增加对象的引用计数。
当所有指向对象的 std::shared_ptr 都销毁后,std::weak_ptr 将自动失效,不再指向任何对象。 - 解决循环引用:
循环引用是指两个或多个对象彼此持有对方的 std::shared_ptr,导致对象之间形成了环状依赖关系,从而无法释放内存。
通过使用 std::weak_ptr,可以打破循环引用,避免内存泄漏。通常,对象之间的强引用使用 std::shared_ptr,而在需要避免循环引用的情况下,使用 std::weak_ptr 进行弱引用。 - 检查是否有效:
使用 std::weak_ptr 可以通过 lock() 方法来获取一个指向所指对象的 std::shared_ptr,如果对象已经被释放,则返回一个空的 std::shared_ptr。
通过检查 std::shared_ptr 是否为空,可以确定对象是否有效。 - 线程安全:
std::weak_ptr 的操作是线程安全的,可以在多个线程之间安全地共享。 - 用法:
std::weak_ptr 的使用方式类似于 std::shared_ptr,可以通过 std::weak_ptr 的构造函数或 std::shared_ptr 的 weak_ptr 方法来创建。
通过调用 lock() 方法获取 std::shared_ptr 对象,然后使用 std::shared_ptr 对象操作所指对象。 - 总的来说,std::weak_ptr 是用于解决循环引用问题的一种方案,它允许共享对象的所有权,但不会增加对象的引用计数,从而避免了循环引用问题,并提供了一种安全的方式来访问共享对象。
- 这个简化的 MySharedPtr 类实现了基本的智能指针功能,包括动态分配内存、引用计数、拷贝构造、析构等。需要注意的是,这个实现并没有考虑多线程安全等复杂情况,真正的 std::shared_ptr 还包含了更多的功能和优化。
#include <iostream>
template <typename T>
class MySharedPtr {
public:
// 构造函数
explicit MySharedPtr(T* ptr = nullptr) : m_ptr(ptr), m_refCount(new size_t(1)) {}
// 拷贝构造函数
MySharedPtr(const MySharedPtr<T>& other) : m_ptr(other.m_ptr), m_refCount(other.m_refCount) {
(*m_refCount)++;
}
// 析构函数
~MySharedPtr() {
(*m_refCount)--;
if (*m_refCount == 0) {
delete m_ptr;
delete m_refCount;
}
}
// 解引用操作符
T& operator*() const { return *m_ptr; }
// 成员访问操作符
T* operator->() const { return m_ptr; }
// 获取引用计数
size_t use_count() const { return *m_refCount; }
private:
T* m_ptr; // 指向动态分配内存的原始指针
size_t* m_refCount; // 引用计数
};
int main() {
// 使用 MySharedPtr 创建智能指针
MySharedPtr<int> ptr1(new int(42));
MySharedPtr<int> ptr2 = ptr1; // 调用拷贝构造函数
std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;
std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;
return 0;
}