关于智能指针的再学习与理解。
本篇文章深入探讨 C++ 中的智能指针,介绍 std::unique_ptr、std::shared_ptr 和 std::weak_ptr 三种常见的智能指针类型,分别讨论它们的特点、使用场景以及常见的内存管理问题。通过代码示例和个人理解,解释了智能指针如何利用 RAII 技术解决手动内存管理中的困扰,如循环引用、悬空指针等问题,最终帮助开发者更高效地管理内存。
前言
C++
的内存管理始终是一个难题,尤其是引入指针后,共享独享与垃圾回收异常困难。
例如下列代码中,三个指针实际上指向同一个MyClass
实例,将ptr
释放后会出现严重的内存问题,此时的ptr
被置为nullptr
,而ptr
1 和ptr2
成为悬空指针。
class MyClass
{
public:
MyClass() { std::cout << "MyClass Constructor\n"; }
~MyClass() { std::cout << "MyClass Destructor\n"; }
};
int main()
{
MyClass *ptr = new MyClass(); // 创建一个原始指针
MyClass *ptr2 = ptr; // 复制原始指针
MyClass *ptr3 = ptr; // 复制原始指针
delete ptr; // 释放原始指针
}
因而为了方便的解决内存管理,将指针的使用与RAII技术结合,智能指针诞生了。
RAII的核心思想是通过对象的生命周期来管理资源,确保资源在对象创建时分配,在对象销毁时释放。关于该原则,后续会有相关博客文章,敬请期待!
我始终认为智能指针才应该算是真正意义的指针,脱离内容的指针毫无意义,指针作为指向性质的类型,就应该和其内容共享生命周期。
或者说,智能指针是开发用的,而指针是更为底层的类型。就像Vector和基础数组的关系一样。
核心
目前可用的智能指针共有3种,分别是
std::unique_ptr
std::shared_ptr
std::weak_ptr
std::unique_ptr(独享指针)
std::unique_ptr
是独占所有权的智能指针,同一时间内只有一个指针可以拥有所指对象的所有权。它在对象生命周期结束时自动释放资源。可以认为,独享指针和它绑定的对象,是强绑定的,谁都不能离开谁。
用一个示例快速带大家理解
class MyClass
{
public:
MyClass() { std::cout << "MyClass Constructor\n"; }
~MyClass() { std::cout << "MyClass Destructor\n"; }
};
void test()
{
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(); // 创建一个unique_ptr
// std::unique_ptr<MyClass> ptr2 = ptr1; // 错误,unique_ptr不能复制
// ptr1离开作用域,被释放
}
int main()
{
test();
return 0;
}
其达成的效果很明显:
- 独占所有权:确保对象不会被多个指针共享。
- 自动内存释放:对象生命周期结束时自动释放内存。
std::shared_ptr(共享指针)
std::shared_ptr
是一种共享式智能指针,允许多个指针共享同一个对象。当最后一个 std::shared_ptr
被销毁时,所管理的对象才会被销毁。
`实例对象会有一个对应的控制块,里面有引用计数器,记录有几个共享指针指向它,当计数器归零,即自动触发垃圾回收。
class MyClass {
public:
MyClass() { std::cout << "MyClass Constructor\n"; }
~MyClass() { std::cout << "MyClass Destructor\n"; }
};
int main() {
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();//智能指针ptr1指向该类,引用计数器+1;
{
std::shared_ptr<MyClass> ptr2 = ptr1;//智能指针ptr2指向该类,引用计数器+1;
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
}
// ptr2超出作用域,引用计数-1;
std::cout << "Reference count: " << ptr1.use_count() << std::endl;
return 0;// ptr1超出作用域,引用计数-1,归零,垃圾回收;
}
其作用也比较显然:
- 共享所有权:允许多个指针共享同一个对象。
- 智能垃圾回收:自动管理引用计数,在引用计数为零时释放资源。
问题:循环引用
但这儿有一个严重的问题 看下列代码:
class B;
class A
{
public:
std::shared_ptr<B> ptrB;
~A() { std::cout << "A Destructor\n"; }
};
class B
{
public:
std::shared_ptr<A> ptrA;
~B() { std::cout << "B Destructor\n"; }
};
void createCycle()
{
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->ptrB = b;
b->ptrA = a; // 循环引用
}
int main()
{
createCycle();
// A和B的析构函数不会被调用,造成内存泄漏
return 0;
}
这个代码中,在运行createCycle
后,A和B的计数器都为2,分别来源于自己和对方,createCycle
结束后,发现无法析构,陷入了类似死锁的情况。这个情况我们成为循环引用。为了解决这个问题,std::weak_ptr
出现。