智能指针
智能指针
C++ 智能指针是用于自动管理动态资源(主要是堆内存)的对象,能够有效防止资源泄漏和重复释放问题。 常见的智能指针包括 auto_ptr、unique_ptr、shared_ptr 和 weak_ptr 。
📌std::auto_ptr(已废弃)
基本原理
auto_ptr 通过管理权转移的方式避免资源重复释放: 在拷贝时将资源的所有权从一个对象转移给另一个对象,原来的对象指针被置为 nullptr, 确保资源仅被释放一次。
如下例所示,ap1 的所有权被转移给 ap2,ap1 变为 nullptr:
int main()
{
std::auto_ptr<int> ap1(new int(1));
std::auto_ptr<int> ap2(ap1);
*ap2 = 10;
//*ap1 = 20; // error ,ap1 已经为 nullptr
return 0;
}然而,这种隐式的资源转移容易导致悬空指针或程序崩溃,很多公司已禁止使用。 C++11 后 auto_ptr 被 unique_ptr 所取代。
模拟实现
namespace ff
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = nullptr)
:m_ptr(ptr)
{}
~auto_ptr()
{
if (m_ptr != nullptr)
{
std::cout << "delete: " << m_ptr << std::endl;
delete m_ptr;
m_ptr = nullptr;
}
}
auto_ptr(auto_ptr<T>& ap)
:m_ptr(ap.m_ptr)
{
ap.m_ptr = nullptr;
}
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
if (this != &ap)
{
delete m_ptr;
m_ptr = ap.m_ptr;
ap.m_ptr = nullptr;
}
return *this;
}
T& operator*()
{
return *m_ptr;
}
T* operator->()
{
return &m_ptr;
}
private:
T* m_ptr;
};
}📌std::unique_ptr
基本原理
unique_ptr 通过禁止拷贝的方式确保资源的唯一拥有权,从而防止重复释放。如下例:
int main()
{
std::unique_ptr<int> up1 = std::make_unique<int>(0);
//std::unique_ptr<int> up2(up1); // 编译错误
return 0;
}但防拷贝其实也不是一个很好的办法,因为总有一些场景需要进行拷贝。 如果想要共享资源,就需要用到 shared_ptr 了。
模拟实现
namespace ff
{
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr = nullptr)
:m_ptr(ptr)
{}
~unique_ptr()
{
if (m_ptr != nullptr)
{
std::cout << "delete: " << m_ptr << std::endl;
delete m_ptr;
m_ptr = nullptr;
}
}
T& operator*()
{
return *m_ptr;
}
T* operator->()
{
return &m_ptr;
}
// 禁止拷贝构造与赋值
unique_ptr(unique_ptr<T>& up) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& up) = delete;
private:
T* m_ptr;
};
}📌std::shared_ptr
基本原理
shared_ptr 采用引用计数机制管理资源:
- 每块资源关联一个引用计数,记录当前有多少个
shared_ptr实例共享该资源; - 拷贝时引用计数
+1,析构或重新赋值时-1; - 当引用计数降为 ,资源自动释放。
int main()
{
std::shared_ptr<int> sp1 = std::make_shared<int>(1);
{
std::shared_ptr<int> sp2(sp1);
std::cout << sp1.use_count() << std::endl; // 输出2
}
std::cout << sp1.use_count() << std::endl; // 输出1
return 0;
}模拟实现
namespace ff
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
: m_ptr(ptr)
, m_count(new int(1))
{}
~shared_ptr()
{
if (--(*m_count) == 0)
{
if (m_ptr != nullptr)
{
std::cout << "delete: " << m_ptr << std::endl;
delete m_ptr;
m_ptr = nullptr;
}
delete m_count;
m_count = nullptr;
}
}
shared_ptr(shared_ptr<T>& sp)
: m_ptr(sp.m_ptr)
, m_count(sp.m_count)
{
(*m_count)++;
}
shared_ptr<T>& operator=(shared_ptr<T>& sp)
{
if (m_ptr != sp.m_ptr)
{
if (--(*m_count) == 0)
{
if (m_ptr != nullptr)
{
std::cout << "delete:" << m_ptr << std::endl;
delete m_ptr;
delete m_count;
}
}
m_ptr = sp.m_ptr;
m_count = sp.m_count;
(*m_count)++;
}
return *this;
}
int use_count()
{
return *m_count;
}
T& operator*()
{
return *m_ptr;
}
T* operator->()
{
return &m_ptr;
}
private:
T* m_ptr;
int* m_count;
};
}std::shared_ptr的循环引用问题
在某些场景中,多个 shared_ptr 对象可能会相互引用,从而形成循环引用,导致引用计数无法归零,资源泄露。
例如双向链表节点:
struct Point
{
std::shared_ptr<Point> m_prev;
std::shared_ptr<Point> m_next;
int m_value;
Point(int value)
: m_value(value)
, m_prev(nullptr)
, m_next(nullptr)
{}
~Point()
{
std::cout << "delete: " << m_value << std::endl;
}
};
int main()
{
std::shared_ptr<Point> sp1 = std::make_shared<Point>(1);
std::shared_ptr<Point> sp2 = std::make_shared<Point>(2);
sp1->m_next = sp2;
sp2->m_prev = sp1;
return 0;
}上述代码中,sp1 和 sp2 相互引用,导致它们的引用计数始终不为 ,程序结束时资源也不会被释放。
解决方法:使用 std::weak_ptr
std::weak_ptr 是一种不拥有资源的智能指针,可以打破循环引用链。 在设计结构中,建议将一方(通常是“父”指针)改为 weak_ptr。
📌std::weak_ptr
基本原理
weak_ptr 是为了解决 shared_ptr 的循环引用问题而引入的辅助智能指针,它不拥有资源的所有权,不会影响引用计数。
通过 weak_ptr 观察一个由 shared_ptr 管理的对象,可以在需要时临时提升为 shared_ptr 使用, 如果资源已释放,提升会失败,避免了悬空指针和资源泄露。
使用 lock() 方法可以将 weak_ptr 升级为 shared_ptr,如下所示:
int main()
{
std::shared_ptr<int> sp = std::make_shared<int>(10);
std::weak_ptr<int> wp = sp;
if (auto temp = wp.lock()) // 成功提升为 std::shared_ptr
{
std::cout << *temp << std::endl;
}
else
{
std::cout << "资源已释放。" << std::endl;
}
return 0;
}在解决循环引用时,可以将一方的 shared_ptr 替换为 weak_ptr, 如下改写双向链表的例子:
struct Point
{
std::weak_ptr<Point> m_prev; // 弱引用,不影响引用计数
std::shared_ptr<Point> m_next;
int m_value;
Point(int value)
: m_value(value)
, m_prev{}
, m_next(nullptr)
{}
~Point()
{
std::cout << "delete: " << m_value << std::endl;
}
};