旧版的auto_ptr
既然要控制资源的转移,我们可以多定义一个成员对象_owner来标识该成员对象的资源管理权,在调用构造函数创建对象时赋予其资源管理权,即_owner置为true,在调用拷贝构造函数、赋值运算符的时候将其管理权释放,即_owner置为false,就起到了资源管理权的转移
#include<iostream>
using namespace std;
template<class T>
class Autoptr
{
public:
Autoptr<T>(T*ptr = NULL)
:_ptr(ptr)
, _symbol(true)
{}
Autoptr<T>(const Autoptr<T>&a)
:_ptr(a._ptr)
, _symbol(true)
{
a._symbol = false;
}
Autoptr<T>&operator=(const Autoptr<T>&a)
{
if (this != &a)
{
delete this->_ptr;
_ptr = a._ptr;
_symbol = ture;
a._symbol = false;
}
return *this;
}
T&operator*()//解引用操作符重载
{
return *(this->_ptr);
}
T&operator*()const//const型解引用操作符重载
{
return *(this->_ptr);
}
T*operator->()//取地址操作符重载
{
return this->_ptr;
}
T*operator->()const//const型取地址操作符重载
{
return this->_ptr;
}
~Autoptr<T>()
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
_symbol = false;
}
}
private:
T* _ptr;
mutable bool _symbol;//在const修饰的成员函数中要对类的某个数据成员进行修改,该数据成员定义声明必须加mutale关键字。
};
int main()
{
Autoptr<int>p1(new int);//
int i = 1;
if (i == 1)
{
Autoptr<int>p2(p1);
}
*p1 = 10;
system("pause");
return 0;
}
原因是:p2出了if作用域会释放,将自己的空间释放,当再次为p1赋值将导致寻址失败,而导致程序崩溃。
新版的auto_ptr的实现:
继承旧版的auto_ptr实现的思想,将symbol去掉,在使用拷贝构造、赋值运算符重载后直接将原对象置空。
//新版
template<class T>
class Auto_ptr
{
public:
Auto_ptr(T*ptr=NULL)
: _ptr(ptr)
{}
Auto_ptr( /*const*/ Auto_ptr<T>&a)
:_ptr(a._ptr)
{
a._ptr = NULL;
}
Auto_ptr<T>&operator=(const Auto_ptr<T>&a)
{
if (this != &a)
{
delete _ptr;
_ptr = a._ptr;
a._ptr = NULL;
}
return *this;
}
T&operator*()//解引用操作
{
return *(this->_ptr);
}
T&operator*()const//解引用操作
{
return *(this->_ptr);
}
T*operator->()//取地址操作
{
return this->_ptr;
}
T*operator->()const//const取地址操作
{
return this->_ptr;
}
~Auto_ptr<T>()
{
if (_ptr)
{
delete _ptr;
_ptr = NULL;
}
}
private:
T* _ptr;
};
int main()
{
Auto_ptr<int> p(new int(1));
Auto_ptr<int> p1(p);
Auto_ptr<int> p2 = p1;
*p= 10;//这样就会报错误。
system("pause");
return 0;
}
这样的结果虽出乎意料,但也情理之中,对一个已经释放的对象重新赋值,必然导致程序奔溃,这样的程序是非常不安全。
AutoPtr<int> ap1(FunTest());
system("pause");
return 0;
}
AutoPtr<int> FunTest()
{
AutoPtr<int> p(new int(1));
return p;
}
int main()
{
AutoPtr<int> p(AutoPtr<int>(new int(1)));
system("pause");
return 0;
}
在VS2017下,这段程序是正常执行的,在new一个对象时产生了一个无名对象,无名对象是具有常性的,在拷贝构造无名对象时,拷贝构造函数应该用const类型的参数来接收,但是这样的程序却成功运行了,原因就在于VS2017下编译器对其进行了优化,即不会再去调用它的拷贝构造函数,而是直接取调用构造函数。
为了验证这个程序的跨平台效果,我决定在g++上测试一下程序的正确性:
我们发现在g++上测试编译阶段都过不了,用一个具有常性的对象去拷贝构造一个对象时,必须用const类型对象的引用来接收具有const性质的的对象。