C++智能指针总结一——auto_ptr
1.前言
本文旨在旋风式的介绍一下智能指针的基本用法,然后在一定程度解析一下它们的底层原理与源码(源码未必要全部看懂,明白关键部分就行了)。
2.为什么要使用智能指针
其实智能指针就是为了解决动态分配的内存得不到及时的释放的问题而出现的一种机制。
我们知道在C与C++语言中,是可以使用指针来操纵内存的。使用malloc或new可以在堆上分配一块内存。但使用malloc或new分配的内存是不会被操作系统主动回收的,需要程序员自己手动释放。如果程序员分配了太多内存又不及时释放,就可能导致堆溢出,进而引发程序奔溃。
临时变量在自己的生命期结束后会被销毁,临时对象也是如此,而智能指针正是利用这一原理实现的。
使用智能指针时,我们会创建一个智能指针的对象,分配的内存地址会被储存在智能指针对象的一个私有指针变量里。从而将不会被主动释放的动态内存与对象的生命周期绑定。
从逻辑上看,该动态内存的生存周期应该是和创建的对象一样的,所以当对象的生命周期结束时,动态内存也应该被释放。实际上也确实是这样做的。对象的生命周期结束时,会调用它自己的析构函数,同时,因为动态内存的地址储存在对象的私有变量里,所以该动态内存也会被delete。
总结来说,智能指针其实就是将动态内存分配的首地址赋值到一个对象的私有变量上,从而将动态内存的生命周期与对象绑定,并通过对象的析构函数来自动释放该动态内存的一种机制。
底层源码:
auto_ptr(auto_ptr_ref<_Ty> _Right) noexcept {
_Ty* _Ptr = _Right._Ref;
_Right._Ref = nullptr; // release old
_Myptr = _Ptr; // 将动态分配的内存首地址赋值给类的私有成员
}
~auto_ptr() noexcept { //析构函数,释放分配的内存
delete _Myptr;
}
private:
_Ty* _Myptr; //私有成员
2.auto_ptr
1.API用法
1.基本使用: auto_ptr<类型> 变量名(new 类型)
#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
class DEMO {
public:
DEMO() {
cout << "创建text" << endl;
}
~DEMO() {
cout << "析构text" << endl;
}
private:
int data = 0;
};
int main(void) {
auto_ptr<int> ptr(new int);
auto_ptr<DEMO> ptr1(new DEMO());
auto_ptr<string> ptr2(new string);
system("pause");
return 0;
}
2.release
int main(void) {
auto_ptr<int> ptr(new int);
auto_ptr<DEMO> ptr1(new DEMO());
auto_ptr<string> ptr2(new string);
int* tmp = ptr.release(); //解除动态内存与智能指针对象的绑定
system("pause");
return 0;
}
底层源码:
_Ty* release() noexcept {
_Ty* _Tmp = _Myptr;
_Myptr = nullptr;
return _Tmp;
}
即解除动态内存与智能指针对象的绑定,并返回动态内存的首地址,我们可以通过创建一个指针接收返回值来获得对动态内存的控制。使用relase之后动态内存需要我们手动释放。
值得注意的是如果你没有使用指针接收返回的指针,那么动态分配的那块内存在程序结束之前都不会也将不能被释放。
3.reset
#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
class DEMO {
public:
DEMO(int data) {
this->data = data;
cout << "创建text" << endl;
cout << "data=" << data << endl;
}
~DEMO() {
cout << "析构text" << endl;
cout << "data=" << data << endl;
}
private:
int data;
};
int main(void) {
auto_ptr<int> ptr(new int);
auto_ptr<DEMO> ptr1(new DEMO(1));
auto_ptr<string> ptr2(new string);
ptr1.reset(new DEMO(2)); //先析构DEMO(1),再与DEMO(2)绑定
system("pause");
return 0;
}
底层源码:
void reset(_Ty* _Ptr = nullptr) noexcept { // destroy designated object and store new pointer
if (_Ptr != _Myptr) {
delete _Myptr;
}
_Myptr = _Ptr;
}
函数功能:
重新绑定一块动态内存,如果新地址与原地址不一致,就析构原来的,绑定新的地址。
2.使用建议
1.不要将auto_ptr 变量定义为全局变量或指针:
如:
#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
auto_ptr<int> ptr1(new int); //全局的智能指针
int main(void) {
auto_ptr<int>* ptr = new auto_ptr<int>(new int); //指向智能指针对象的指针变量
system("pause");
return 0;
}
如果真正理解了智能指针的原理就会明白为什么这样做是不好的。
原因就在于这样做智能指针和普通指针是没有区别的,并不能发挥智能指针的功能(还不如用普通指针,省空间)。
3.使用方式
#include <Windows.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
struct Demo {
int data;
};
int main(void) {
auto_ptr<struct Demo> ptr(new struct Demo);
ptr->data = 5;
cout << "ptr->data=" << ptr->data << endl;
cout << "*ptr.data=" << (*ptr).data << endl;
system("pause");
return 0;
}
智能指针使用 * 与 -> 运算符的方式与普通指针是一样的,但我们知道,代码中的ptr其实是类实例化的一个对象,那为什么它能和普通指针一样使用 * 与 ->呢。
这就要归功于C++强大的运算符重载功能了。auto_ptr类实现了 * 号与 -> 号的运算符重载,让他们能和普通指针一样能使用 * 号与 -> 号。
_NODISCARD _Ty& operator*() const noexcept { //重载 *号
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Myptr, "auto_ptr not dereferenceable");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return *get();
}
_NODISCARD _Ty* operator->() const noexcept { //重载 ->号
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Myptr, "auto_ptr not dereferenceable");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return get();
}