个人主页:PingdiGuo_guo
收录专栏:C++干货专栏
大家好呀,我是PingdiGuo_guo,今天我们来学习一下智能指针。
文章目录
1.智能指针的概念
智能指针是在C++中用于管理堆内存的对象,它是C++标准库提供的一个抽象层,用来封装原始的裸指针。智能指针本质上是一个类,它有一个指向动态分配对象的成员变量,并且该类重载了运算符和其他必要的方法,以便在适当的时间自动释放所指向的对象。这相当于把资源管理的责任从程序员转移到了智能指针对象上,遵循了RAII(Resource Acquisition Is Initialization)的原则。
2.智能指针的思想
智能指针的核心思想在于将内存资源的生命周期绑定到某个特定的对象(智能指针对象)上,而不是依赖于程序执行流程中的某些逻辑判断来决定何时释放内存。当智能指针对象不再需要(例如,出了作用域或被删除时),它会自动清理自己管理的内存,从而消除因忘记释放内存而导致的内存泄漏问题。
3.智能指针的作用
3.1 自动内存管理
智能指针能确保在其生命周期结束后自动释放其管理的堆内存资源。
3.2 共享所有权
某些类型的智能指针(如std::shared_ptr)支持多个智能指针共享同一份动态分配的内存,只有当所有共享此内存的智能指针都被销毁时,内存才会真正释放。
3.3 避免悬挂指针
智能指针通过维护内部计数器或其他机制,可以防止在内存已经被释放后仍访问该内存的情况,从而避免悬挂指针错误。
4.智能指针的操作
4.1 初始化
创建智能指针时,可以通过传递给构造函数一个new表达式的结果来初始化智能指针,使其开始管理一块内存。
std::unique_ptr<int> uptr(new int(42));
4.2 拷贝与移动
- 普通拷贝:对于std::unique_ptr来说,拷贝构造函数和赋值运算符被禁用,因为唯一所有权不允许复制;而对于std::shared_ptr,拷贝会增加引用计数。
- 移动操作:智能指针通常支持移动构造函数和移动赋值运算符,它们会转移资源的所有权,使得原智能指针变为空指针,而目标智能指针接管资源管理。
4.3 访问与解引用
智能指针提供了类似于普通指针的操作,可以通过->运算符访问成员,或者通过解引用运算符*直接访问对象。
std::cout << *uptr; // 输出42
uptr->some_method(); // 调用智能指针所指对象的方法
5.智能指针的分类
智能指针通常分为以下几类:
5.1 unique_ptr(独占指针)
unique_ptr是C++11引入的一种独占式智能指针,它通过使用“独占权”来确保资源的单一所有者。这意味着在同一时间只有一个unique_ptr可以拥有一个资源,并且当unique_ptr销毁时,它所拥有的资源会被自动释放。
#include <memory>
int main() {
std::unique_ptr<int> ptr(new int);
*ptr = 10;
// unique_ptr会在作用域结束时自动释放资源
return 0;
}
5.2 shared_ptr(共享指针)
shared_ptr是一种通过“引用计数”来共享资源的智能指针。它可以有多个shared_ptr同时拥有同一个资源,每个shared_ptr都会维护一个计数器,用于追踪资源的引用数。只有当引用计数降为0时,资源才会被释放。
#include <memory>
int main() {
std::shared_ptr<int> ptr1(new int);
std::shared_ptr<int> ptr2 = ptr1;
*ptr1 = 10;
*ptr2 = 20;
// 此时引用计数为2
return 0;
}
5.3 weak_ptr(弱指针)
weak_ptr是一种特殊的共享指针,它可以“观测”shared_ptr的资源,而不拥有该资源。weak_ptr并不会影响资源的生命周期,当其所观测的资源被释放后,weak_ptr会自动变为空指针。
#include <memory>
int main() {
std::shared_ptr<int> ptr(new int);
std::weak_ptr<int> weakPtr = ptr;
// 此时weakPtr观测ptr所指向的资源
ptr.reset();
// 此时资源已被释放,weakPtr变为空指针
return 0;
}
5.4 auto_ptr(自动指针)
auto_ptr是C++98中提供的一种智能指针,用于进行简单的资源管理。与unique_ptr类似,auto_ptr也是一种独占式的智能指针,但它的语义和行为比较特殊,容易引发一些不可预料的问题。由于其语义问题,auto_ptr在C++11中已被弃用。
#include <memory>
int main() {
std::auto_ptr<int> ptr(new int);
*ptr = 10;
// auto_ptr会在作用域结束时自动释放资源
return 0;
}
总结起来,智能指针是C++中一种方便且安全的资源管理工具。通过使用智能指针,我们能够避免手动管理内存资源的复杂性和潜在错误,提高代码的可靠性和可维护性。
6.智能指针的练习
6.1题目
实现一个具有引用计数功能的智能指针类,用于管理动态分配的整型数据。
6.2步骤
1. 创建一个智能指针类SmartPointer,其中包含一个指向整型数据的指针和一个整型的引用计数变量。在构造函数中,为指针分配内存并将引用计数初始化为1。
#include <iostream>
class SmartPointer {
public:
SmartPointer(int* pData) : m_pData(pData), m_pCount(new int(1)) {
std::cout << "Create SmartPointer with data " << *m_pData << std::endl;
}
// 省略其他成员函数
private:
int* m_pData;
int* m_pCount;
};
2. 实现拷贝构造函数,它将另一个SmartPointer对象作为参数。在拷贝构造函数中,将指针和引用计数变量分别指向原始对象的成员,并将引用计数递增。
SmartPointer(const SmartPointer& other) : m_pData(other.m_pData), m_pCount(other.m_pCount) {
++(*m_pCount);
std::cout << "Copy SmartPointer with data " << *m_pData << std::endl;
}
3. 实现析构函数,用于在引用计数变为0时释放内存。在析构函数中,将引用计数递减,若引用计数为0,则释放指针指向的内存。
~SmartPointer() {
--(*m_pCount);
if (*m_pCount == 0) {
delete m_pData;
delete m_pCount;
std::cout << "Delete SmartPointer with data " << *m_pData << std::endl;
}
}
4. 重载赋值运算符,用于实现对象的赋值操作。在赋值运算符中,需要先递减原对象的引用计数,然后再递增赋值对象的引用计数。
SmartPointer& operator=(const SmartPointer& other) {
if (this != &other) {
--(*m_pCount);
if (*m_pCount == 0) {
delete m_pData;
delete m_pCount;
std::cout << "Delete SmartPointer with data " << *m_pData << std::endl;
}
m_pData = other.m_pData;
m_pCount = other.m_pCount;
++(*m_pCount);
std::cout << "Assign SmartPointer with data " << *m_pData << std::endl;
}
return *this;
}
5. 创建一个测试函数,在函数中创建多个SmartPointer对象,并进行赋值操作。
void test() {
SmartPointer sp1(new int(5));
SmartPointer sp2(sp1);
SmartPointer sp3(new int(10));
sp3 = sp2;
}
6. 在主函数中调用测试函数并观察输出结果。
int main() {
test();
return 0;
}
6.3完整代码
#include <iostream>
class SmartPointer {
public:
SmartPointer(int* pData) : m_pData(pData), m_pCount(new int(1)) {
std::cout << "Create SmartPointer with data " << *m_pData << std::endl;
}
SmartPointer(const SmartPointer& other) : m_pData(other.m_pData), m_pCount(other.m_pCount) {
++(*m_pCount);
std::cout << "Copy SmartPointer with data " << *m_pData << std::endl;
}
~SmartPointer() {
--(*m_pCount);
if (*m_pCount == 0) {
delete m_pData;
delete m_pCount;
std::cout << "Delete SmartPointer with data " << *m_pData << std::endl;
}
}
SmartPointer& operator=(const SmartPointer& other) {
if (this != &other) {
--(*m_pCount);
if (*m_pCount == 0) {
delete m_pData;
delete m_pCount;
std::cout << "Delete SmartPointer with data " << *m_pData << std::endl;
}
m_pData = other.m_pData;
m_pCount = other.m_pCount;
++(*m_pCount);
std::cout << "Assign SmartPointer with data " << *m_pData << std::endl;
}
return *this;
}
private:
int* m_pData;
int* m_pCount;
};
void test() {
SmartPointer sp1(new int(5));
SmartPointer sp2(sp1);
SmartPointer sp3(new int(10));
sp3 = sp2;
}
int main() {
test();
return 0;
}
7.注意事项
使用智能指针时,有一些注意事项需要注意:
1.避免循环引用:循环引用指的是两个或多个对象相互持有对方的智能指针,导致引用计数始终不为0,从而造成内存泄漏。为了避免循环引用,应该尽量使用弱引用(std::weak_ptr
)或者使用裸指针来解除循环引用。
2.避免使用裸指针:为了避免手动管理内存和潜在的内存泄漏,应该尽量使用智能指针来管理动态分配的内存。避免将裸指针传递给智能指针,以防止多个智能指针管理同一个内存块。
3.不要将裸指针和智能指针混合使用:在代码中,应该始终使用智能指针来管理动态分配的内存。避免将裸指针和智能指针混合使用,否则可能导致重复释放内存或内存泄漏。
4.使用std::make_shared
和std::make_unique
来创建智能指针:std::make_shared
和std::make_unique
是用来创建智能指针的函数模板,它们可以保证在创建智能指针时同时分配内存,避免了显式地使用new
操作符。
5.当需要使用原始指针时,使用get
获取原始指针:在某些情况下,可能需要将智能指针转换为原始指针。可以使用get
成员函数来获取智能指针内部的原始指针。
6.避免在多线程中共享智能指针:智能指针的引用计数机制在多线程中可能引发竞争条件。如果需要在多线程中共享智能指针,应该采取适当的同步措施来确保线程安全。
总之,使用智能指针可以减少内存泄漏的风险,简化内存管理,但也需要注意上述的注意事项来避免潜在的问题。
8.智能指针和常规指针的对比
8.1功能和使用方式
智能指针:智能指针是C++标准库提供的一种数据类型,用于管理动态分配的内存资源。
智能指针可以自动释放内存资源,避免内存泄漏的问题。
智能指针一般采用引用计数的方式,当没有引用指向对象时,会自动释放内存。
智能指针可以像常规指针一样操作对象,可以通过操作符重载来访问对象的成员。
常规指针:常规指针是C/C++语言中的基本概念,用于存储对象的内存地址。
常规指针需要手动管理内存的申请和释放,容易出现内存泄漏和悬空指针的问题。
常规指针可以进行指针运算,如加减操作符来移动指针的位置。
8.2内存管理
智能指针:智能指针通过引用计数或其他方式来自动管理内存,在对象不再被引用时自动释放内存,避免了内存泄漏和悬空指针的问题。
常规指针:常规指针需要手动申请和释放内存,容易出现内存泄漏和悬空指针的问题。
8.3安全性
智能指针:智能指针具有类型安全性,可以在编译时检查类型是否匹配,避免了类型错误的问题。
常规指针:常规指针没有类型安全性,可以进行任意类型的转换,容易出现类型错误。
8.4总结
智能指针相比于常规指针具有自动内存管理、类型安全等优点,能够提高程序的可靠性和安全性。然而,智能指针也会带来一些额外的开销和循环引用等问题。常规指针更为灵活,但需要手动管理内存,容易出现内存泄漏和悬空指针等问题。选择使用智能指针还是常规指针要根据具体的需求和场景来决定。
9.总结
本篇博客到这里就结束了,感谢大家的支持与观看,如果有好的建议欢迎留言,谢谢大家啦!