概述
auto_ptr是c++98提出的智能指针,其可以帮助我们管理动态分配的空间。但是,注意auto_ptr存在一定的问题(后面会说),c++11提出的unique_ptr已经完全取代了auto_ptr所以在可以使用unique_ptr的情况下,建议使用unique_ptr。
使用auto_ptr需要导入头文件 #include <memory>
1. 定义auto_ptr对象
- auto_ptr<类型> 名称(new 空间),// 类型: 智能指针指向的类型,空间:智能指针管理的空间
- auto_ptr<int> p1(new int()); // 创建一个p1的智能指针对象,其指向int类型的数据,动态开辟一个int类型的空间作为参数(其内部的指针会指向这片空间)
- auto_ptr<int> p1; // 创建一个空对象,指向int类型的数据,其内部的指针会指向NULL
2. 使用auto_ptr
其实使用智能指针和普通指针的方法是一样的,对于普通指针最常用的就是*和->,对于智能指针也同样是适用的,因为其内部对这些操作符进行了重载。
auto_ptr<int> p1(new int(1)); // 只能指针p1指向的空间中存放的值为1。
cout << *p1 << endl; // 我们同样对p1进行*解引用就可以取到内部存放的值。
class _Ptr {
public:
_Ptr() {
nub = 10;
cout << __FUNCTION__ << endl;
}~_Ptr() {
cout << __FUNCTION__ << endl;
}int getNub()const {
return nub;
}private:
int nub;
};void main() {
auto_ptr<_Ptr> p1(new _Ptr());cout << p1->getNub() << endl;
cout << (*p1).getNub() << endl;;
}上面代码中定义了一个类,我们使用智能指针p1指向这个类的对象。我们想要访问其内部的成员方法可以使用->直接访问,也可以解引之后用.访问,和普通指针是一样的。
3.auto_ptr中的函数
1)get()函数
- _Ptr* get(); // 这个函数返回一个指向当前智能指针所管理内存的指针。
我们需要使用一个此类型的指针接收这片空间,当然也可以不用,但是注意如果接收之后,智能指针释放了这块内存,我们就不能使用指针访问了。
例: auto_ptr<int> p1(new int()); // 指向int的智能指针int* i = p1.get(); // 调用get()函数,返回一个指向上面动态开辟的空间的指针。
2) reset()函数
- 这是一个重设函数,就是重新设置当前的智能指针,传入一个空间的地址,如果这个地址和之前指向的地址不一样,那么reset函数就会让智能指针指向传入的内存,释放之前指向的空间。(当然如果传入的地址空间还一样,那自然不需要重设)
- void reset(ptr); // 如果传入的地址和之前指向的地址不同,那么就释放之前的地址,指向新传入的地址。
- void reset(); // 什么也不传入,就是释放之前指向的空间,然后指向null。
所以,reset()函数不仅可以用来重新设置智能指针的指向,还可以将智能指针置空。
例:
auto_ptr<int> p1(new int);p1.reset(); // 将指针置空
p1.reset(new int()); // 指向另外一片空间
3)release()函数
我们说过,智能指针的作用就是帮助我们管理动态分配的空间,但是如果我们不希望它们管理了,想要自己管理,这时候就需要使用release()函数了。
这个函数的作用就是收回智能指针对动态空间的管理权。
- _Ptr* release(); // 此函数会返回智能指针指向的空间,然后将自己的指针置为null。也就是,智能指针不再指向这个空间,不再管理它了。
我们需要用一个同类型的指针接收这块内存,接过管理权,但是记住,如果这样这块内存就得你自己手动释放了。(注意: 如果你接收,就造成内存泄露了)
例:auto_ptr<int> p1(new int);
int* p2 = p1.release(); // 使用p2接收其返回的指针,我们自己管理这片空间。
4)赋值函数
就是赋值运算符的重载函数,对于auto_ptr的赋值重载函数需要注意。
假设存在两个auto_ptr对象,p1和p2。
p1 = p2 ; // 会调用p1的赋值运算符重载函数,进行赋值
上面的过程很好理解,但是这并不和普通指针的赋值一样,对于普通指针p1和p2最终会直系那个同一片内存,但是对于auto_ptr不同。
它会先将p2指向null(调用p2的release()函数),如果p2和p1指向的空间不一样,就释放掉p1的空间,让其指向p2的空间(调用p1的reset()函数)。
其实释放掉p1的空间是正常,因为,对于普通指针,如果p1指向一片空间,p2也指向一个,直接p1=p2,会导致p1对应的空间发生泄露,所以赋值之前应该delete p1。只不过是智能指针做了这个事。
但是,我们需要注意的是,p2指向了null,这和我们平时使用指针是不一样的。
4. auto_ptr的问题
auto_ptr存在一些问题,这导致它在c++11被抛弃,被unique_ptr取代,所以能用unique_ptr的地方就使用unique_ptr。
问题一:
- 当两个智能指针管理同一片空间(拷贝构造函数),如果一个析构会影响另外一个
下面的代码就会出现这样的问题
#include <iostream>
#include <stdlib.h>
#include <memory>
using namespace std;
class _Ptr {
public:
_Ptr() {
nub = 10;
cout << __FUNCTION__ << endl;
}
~_Ptr() {
cout << __FUNCTION__ << endl;
}
int getNub()const {
return nub;
}
private:
int nub;
};
int main(void) {
auto_ptr<_Ptr> p1(new _Ptr());
{
auto_ptr<_Ptr> p2(p1);
}
p1->getNub();
system("pause");
return 0;
}
在main函数中,我们定义一个智能指针p1指向了一片空间,然后我们在{}内部重新定义了一个智能指针另其指向和p1的同一片内存。
当{}的作用域结束,p2的生命周期就结束l(局部变量的声明周期就是{开始,}结束),调用其析构函数析构对象,同时释放了其管理的空间。
但是p1和p2管理同一片空间,在后面代码中,我们还希望访问p1中的数据,可是空间已经被p2释放了,也就是此时p1并没有指向空间了。我们还去访问自然就会出错了。
执行,程序会终端,提示: auto_ptr not dereferencable;
问题二:
就是当我们使用容器存储智能指针对象的时候,是不符合容器的规则的,比如容器允许赋值。
看下面的代码
#include <iostream>
#include <stdlib.h>
#include <memory>
#include <vector>
using namespace std;
int main(void) {
vector<auto_ptr<int>> v1;
auto_ptr<int> p1(new int(1));
auto_ptr<int> p2(new int(1));
/*
这样写是不对的,我们需要使用move()函数将对象移动成右值才能成功插入
v1.push_back(p1);
v1.push_back(p2);
*/
v1.push_back(std::move(p1));
v1.push_back(std::move(p2));
// 可以访问
cout << *v1[0] << endl;
cout << *v1[1] << endl;
v1[0] = v1[1];
// 错误
cout << *v1[0] << endl;
cout << *v1[1] << endl;
system("pause");
return 0;
}
上面代码中,我们使用vector容器存放智能指针对象,(插入的时候需要使用move()函数现将对象移动成右值才行,否则会出问题) 然后去访问每个位置智能指针的值,第一次访问都是正常的。
但是当我们将vector中的一个元素赋值给另外一个元素的时候,再输出就会出错。
原因就是问题一造成的,两元素赋值的时候,将后面元素置为null了,当然访问不了其内部元素。但是,对于vector容器的特性,是允许这样操作的,所以如果存放auto_ptr的对象,这样是不被允许的。
为什么在插入数据的时候需要使用move()函数?
原因主要是因为,auto_ptr的构造函数使用关键字 explicit 限定的,这就说明我们在auto_ptr类创建对象的时候只能显示创建,不能隐式创建。 -- 具体看关键字explicit的讲解
auto_ptr<int> p1(new int()); // 显示创建auto_ptr<int> p1 = auto_ptr<int>(new int()); // 隐式创建
而vector中的push_back()只有两种重载函数,一种参数为const auto_ptr<int>&另一种为auto_ptr<int>&& (右值引用)
我们之前说到过,const的引用是会开辟空间的,和普通的引用不同,所以将p1作为参数传入,就相当于 auto_ptr<int> p1 = auto_ptr<int>(new int()); 是这种情况,但是auto_ptr是不允许这样的,所以是错误的, -- 注意: 对于普通的引用还是取别名,不会再开辟空间,但是const的引用会再开辟空间,将对应的数据拷贝一份,就相当于创建对象了。
这样我们只能使用另外一个重载,它接受一个右值,左值是不行的,但是p1是一个左值,所以我们需要使用move()函数将它变为右值才行。
问题三:
问题三就是auto_ptr不能指向数组类型 -- 但是unique_ptr是可以的。
比如: auto_ptr<int []> p1(new int[5]); // 是错误的。