START
Hi,大家好!今天我们来聊一聊C++中的智能指针。
在谈到学习C++时,好多人都说它特别难,说它复杂。很可能有一部分原因就是C++的内存管理,在程序运行过程中很容易就会出现内存泄漏。然而从C++11引入的智能指针这一问题得到解决。
C++11引入了三种智能指针:
std::shared_ptr
std::weak_ptr
std::unique_ptr
1、std::shared_ptr
std::shared_ptr
是用于管理动态分配的资源,实现自动内存管理。它是一个引用计数型智能指针,允许多个 shared_ptr
对象共享同一块动态分配的内存,并在不再需要时自动释放。
以下是 std::shared_ptr
的一些重要特点和用法:
引用计数:
std::shared_ptr
使用引用计数来跟踪共享的资源的使用情况。每个std::shared_ptr
对象都包含一个计数器,记录有多少个std::shared_ptr
对象共享同一块内存。安全性:
std::shared_ptr
通过引用计数机制来确保在所有持有该资源的std::shared_ptr
对象被销毁后,资源会被释放。这避免了内存泄漏和空悬指针等问题。拷贝和赋值:
std::shared_ptr
支持拷贝和赋值操作,当进行拷贝时,计数器会增加;当进行销毁或重新赋值时,计数器会减少。当计数器减少到 0 时,资源会被释放。动态分配的资源:
std::shared_ptr
通常用于管理动态分配的资源,如内存、文件句柄等。它不仅可以管理指针指向的内存,还可以管理自定义的资源,如自定义的释放器等。线程安全性:
std::shared_ptr
在多线程环境下是线程安全的,可以被多个线程同时访问和操作,不需要额外的同步机制。
使用 std::shared_ptr
可以有效地管理动态分配的资源,避免内存泄漏和空悬指针等问题,并且可以方便地进行资源的共享和传递。然而,要注意避免循环引用的问题,这可能导致资源无法释放。通常可以使用 std::weak_ptr
来解决循环引用的问题。
下面来看一个使用 std::shared_ptr
的简单示例:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() {
std::cout << "Constructor" << std::endl;
}
~MyClass() {
std::cout << "Destructor" << std::endl;
}
void doSomething() {
std::cout << "Doing something..." << std::endl;
}
};
int main() {
// 创建一个动态分配的 MyClass 对象,并用 shared_ptr 管理
std::shared_ptr<MyClass> ptr1(new MyClass);
// 创建另一个 shared_ptr,与 ptr1 共享同一块内存
std::shared_ptr<MyClass> ptr2 = ptr1;
// 使用箭头运算符访问对象成员函数
ptr1->doSomething();
// 当 ptr1 和 ptr2 被销毁时,资源会被自动释放
return 0;
}
在这个示例中,我们首先创建了一个动态分配的 MyClass
对象,并用 std::shared_ptr
管理它。然后,我们创建了另一个 std::shared_ptr
,与第一个 std::shared_ptr
共享同一块内存。这意味着两个 std::shared_ptr
对象共享同一个计数器和同一块内存。最后,我们通过箭头运算符访问了 MyClass
对象的成员函数,并且在程序结束时,由于 ptr1
和 ptr2
被销毁,MyClass
对象的资源会被自动释放。
这个示例展示了 std::shared_ptr
的基本用法,包括对象的创建、拷贝、访问成员函数以及自动资源管理。
2、std::weak_ptr
std::weak_ptr
是用于解决 std::shared_ptr
的循环引用问题。std::weak_ptr
允许共享资源但不拥有它,它是 std::shared_ptr
的弱引用。
与 std::shared_ptr
不同,std::weak_ptr
并不增加引用计数,因此它不会影响资源的生命周期。当最后一个 std::shared_ptr
指向的资源被释放后,所有相关联的 std::weak_ptr
对象都会自动失效,指向空指针。
以下是 std::weak_ptr
的一些重要特点和用法:
弱引用:
std::weak_ptr
是一个弱引用,它不增加资源的引用计数,因此不会影响资源的生命周期。共享资源:
std::weak_ptr
允许与std::shared_ptr
共享同一块内存,但不拥有它。它通常用于解决循环引用的问题,防止资源无法释放。检查是否有效: 可以使用
std::weak_ptr
的expired()
方法来检查与之关联的资源是否有效。如果资源已经释放,则expired()
返回true
,否则返回false
。获取强引用: 可以使用
std::weak_ptr
的lock()
方法来获取与之关联的资源的强引用(即std::shared_ptr
对象)。如果资源仍然有效,则lock()
返回一个有效的std::shared_ptr
;如果资源已经释放,则返回一个空的std::shared_ptr
。
以下是一个示例,展示了 std::weak_ptr
的基本用法:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() {
std::cout << "Constructor" << std::endl;
}
~MyClass() {
std::cout << "Destructor" << std::endl;
}
};
int main() {
std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weakPtr = sharedPtr;
// 检查 weakPtr 是否有效
if (!weakPtr.expired()) {
// 获取强引用
std::shared_ptr<MyClass> strongPtr = weakPtr.lock();
if (strongPtr) {
// 访问资源
std::cout << "Resource is valid." << std::endl;
}
}
// sharedPtr 被销毁,资源释放
sharedPtr.reset();
// 再次检查 weakPtr 是否有效
if (weakPtr.expired()) {
std::cout << "Resource is expired." << std::endl;
}
return 0;
}
在这个示例中,我们首先创建了一个 std::shared_ptr
来管理动态分配的资源,然后创建了一个 std::weak_ptr
对象与之共享同一块内存。我们使用 expired()
方法检查了 std::weak_ptr
是否有效,并使用 lock()
方法获取了与之关联的资源的强引用。最后,我们释放了 sharedPtr
,并再次检查了 std::weak_ptr
是否有效。
通过使用 std::weak_ptr
,我们可以解决 std::shared_ptr
的循环引用问题,确保资源能够正确释放,避免内存泄漏。
3、std::unique_ptr
std::unique_ptr
是用于管理动态分配的资源。与 std::shared_ptr
不同,std::unique_ptr
是独占所有权的智能指针,即一个 std::unique_ptr
对象独占一个动态分配的资源,并负责在其生命周期结束时释放该资源。
以下是 std::unique_ptr
的一些重要特点和用法:
独占所有权:
std::unique_ptr
是独占所有权的智能指针,一次只能有一个std::unique_ptr
对象拥有一个动态分配的资源。资源所有权转移:
std::unique_ptr
支持资源所有权的转移。可以通过std::move
函数将一个std::unique_ptr
对象的所有权转移到另一个对象。禁止拷贝和赋值:
std::unique_ptr
对象禁止拷贝和赋值操作。这意味着不能对std::unique_ptr
对象进行拷贝构造或赋值操作,从而确保资源的独占性。动态分配的资源:
std::unique_ptr
通常用于管理动态分配的资源,如内存、文件句柄等。它不仅可以管理指针指向的内存,还可以管理自定义的资源,如自定义的释放器等。移动语义:
std::unique_ptr
支持移动语义,可以高效地将资源转移给其他std::unique_ptr
对象。这意味着在传递std::unique_ptr
参数时,不会发生资源的拷贝,而是发生所有权的转移。
以下是一个示例,展示了 std::unique_ptr
的基本用法:
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass() {
std::cout << "Constructor" << std::endl;
}
~MyClass() {
std::cout << "Destructor" << std::endl;
}
void doSomething() {
std::cout << "Doing something..." << std::endl;
}
};
int main() {
// 创建一个动态分配的 MyClass 对象,并用 unique_ptr 管理
std::unique_ptr<MyClass> ptr(new MyClass);
// 调用对象的成员函数
ptr->doSomething();
// 当 ptr 被销毁时,资源会被自动释放
return 0;
}
在这个示例中,我们首先创建了一个动态分配的 MyClass
对象,并用 std::unique_ptr
管理它。然后,我们通过箭头运算符调用了 MyClass
对象的成员函数,并且在程序结束时,由于 ptr
被销毁,MyClass
对象的资源会被自动释放。
std::unique_ptr
,我们可以方便地管理动态分配的资源,并避免内存泄漏和空悬指针等问题。END