【转】浅析auto_ptr

更新时间:2023-05-12 03:59:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

智能指针,auto_prt的基本用法,及其示例

C++的auto_ptr auto_ptr所做的事情,就是动态分配对象以及当对象不再需要时自动执行清理。

1 构造函数与析构函数 auto_ptr在构造时获取对某个对象的所有权(ownership),在析构时释放该对象。我们可以这样使用auto_ptr来提高代码安全性:

int* p = new int(0);

auto_ptr<int> ap(p);

从此我们不必关心应该何时释放p, 也不用担心发生异常会有内存泄漏。

这里我们有几点要注意:

1) 因为auto_ptr析构的时候肯定会删除他所拥有的那个对象,所有我们就要注意了,一个萝卜一个坑,两个auto_ptr不能同时拥有同一个对象。像这样:

int* p = new int(0);

auto_ptr<int> ap1(p);

auto_ptr<int> ap2(p);

因为ap1与ap2都认为指针p是归它管的,在析构时都试图删除p, 两次删除同一个对象的行为在C++标准中是未定义的。所以我们必须防止这样使用auto_ptr.

2) 考虑下面这种用法:

int* pa = new int[10];

auto_ptr<int> ap(pa);

因为auto_ptr的析构函数中删除指针用的是delete,而不是delete [],所以我们不应该用auto_ptr来管理一个数组指针。

智能指针,auto_prt的基本用法,及其示例

3) 构造函数的explicit关键词有效阻止从一个“裸”指针隐式转换成auto_ptr类型。

4) 因为C++保证删除一个空指针是安全的, 所以我们没有必要把析构函数写成: ~auto_ptr() throw()

{

if(ap) delete ap;

}

2 拷贝构造与赋值

与引用计数型智能指针不同的,auto_ptr要求其对“裸”指针的完全占有性。也就是说一个”裸“指针不能同时被两个以上的auto_ptr所拥有。那么,在拷贝构造或赋值操作时,我们必须作特殊的处理来保证这个特性。auto_ptr的做法是“所有权转移”,即拷贝或赋值的源对象将失去对“裸”指针的所有权,所以,与一般拷贝构造函数,赋值函数不同, auto_ptr的拷贝构造函数,赋值函数的参数为引用而不是常引用(const reference).当然,一个auto_ptr也不能同时拥有两个以上的“裸”指针,所以,拷贝或赋值的目标对象将先释放其原来所拥有的对象。

这里的注意点是:

1) 因为一个auto_ptr被拷贝或被赋值后, 其已经失去对原对象的所有权,这个时候,对这个auto_ptr的提领(dereference)操作是不安全的。如下:

int* p = new int(0);

auto_ptr<int> ap1(p);

auto_ptr<int> ap2 = ap1;

cout<<*ap1; //错误,此时ap1只剩一个null指针在手了

智能指针,auto_prt的基本用法,及其示例

这种情况较为隐蔽的情形出现在将auto_ptr作为函数参数按值传递,因为在函数调用过程中在函数的作用域中会产生一个局部对象来接收传入的auto_ptr(拷贝构造),这样,传入的实参auto_ptr就失去了其对原对象的所有权,而该对象会在函数退出时被局部auto_ptr删除。如下:

void f(auto_ptr<int> ap){cout<<*ap;}

auto_ptr<int> ap1(new int(0));

f(ap1);

cout<<*ap1; //错误,经过f(ap1)函数调用,ap1已经不再拥有任何对象了。

因为这种情况太隐蔽,太容易出错了, 所以auto_ptr作为函数参数按值传递是一定要避免的。或许大家会想到用auto_ptr的指针或引用作为函数参数或许可以,但是仔细想想,我们并不知道在函数中对传入的auto_ptr做了什么, 如果当中某些操作使其失去了对对象的所有权, 那么这还是可能会导致致命的执行期错误。 也许,用const reference的形式来传递auto_ptr会是一个不错的选择。

2)我们可以看到拷贝构造函数与赋值函数都提供了一个成员模板在不覆盖“正统”版本的情况下实现auto_ptr的隐式转换。如我们有以下两个类

class base{};

class derived: public base{};

那么下列代码就可以通过,实现从auto_ptr<derived>到auto_ptr<base>的隐式转换,因为derived*可以转换成base*类型

auto_ptr<base> apbase = auto_ptr<derived>(new derived);

智能指针,auto_prt的基本用法,及其示例

3) 因为auto_ptr不具有值语义(value semantic), 所以auto_ptr不能被用在stl标准容器中。

所谓值语义,是指符合以下条件的类型(假设有类A):

A a1;

A a2(a1);

A a3;

a3 = a1;

那么

a2 == a1, a3 == a1

很明显,auto_ptr不符合上述条件,而我们知道stl标准容器要用到大量的拷贝赋值操作,并且假设其操作的类型必须符合以上条件。

3 提领操作(dereference)

提领操作有两个操作, 一个是返回其所拥有的对象的引用, 另一个是则实现了通过auto_ptr调用其所拥有的对象的成员。如:

struct A

{

void f();

}

auto_ptr<A> apa(new A);

(*apa).f();

智能指针,auto_prt的基本用法,及其示例

apa->f();

当然, 我们首先要确保这个智能指针确实拥有某个对象,否则,这个操作的行为即对空指针的提领是未定义的。

4 辅助函数

1) get用来显式的返回auto_ptr所拥有的对象指针。我们可以发现,标准库提供的auto_ptr既不提供从“裸”指针到auto_ptr的隐式转换 (构造函数为explicit),也不提供从auto_ptr到“裸”指针的隐式转换,从使用上来讲可能不那么的灵活, 考虑到其所带来的安全性还是值得的。

2) release,用来转移所有权

3) reset,用来接收所有权,如果接收所有权的auto_ptr如果已经拥有某对象, 必须先释放该对象。

5 特殊转换

这里提供一个辅助类auto_ptr_ref来做特殊的转换,按照标准的解释, 这个类及下面 4个函数的作用是:使我们得以拷贝和赋值non-const auto_ptrs, 却不能拷贝和赋值const auto_ptrs. 我无法非常准确的理解这两句话的意义,但根据我们观察与试验,应该可以这样去理解:没有这些代码,我们本来就可以拷贝和赋值non-const的 auto_ptr和禁止拷贝和赋值const的auto_ptr的功能, 只是无法拷贝和赋值临时的auto_ptr(右值), 而这些辅助代码提供某些转换,使我们可以拷贝和赋值临时的auto_ptr,但并没有使const的auto_ptr也能被拷贝和赋值。如下:

auto_ptr<int> ap1 = auto_ptr<int>(new int(0));

智能指针,auto_prt的基本用法,及其示例

auto_ptr<int>(new int(0))是一个临时对象,一个右值,一般的拷贝构造函数当然能拷贝右值,因为其参数类别必须为一个const reference, 但是我们知道,auto_ptr的拷贝函数其参数类型为reference,所以,为了使这行代码能通过,我们引入 auto_ptr_ref来实现从右值向左值的转换。其过程为:

1) ap1要通过拷贝 auto_ptr<int>(new int(0))来构造自己

2) auto_ptr<int>(new int(0))作为右值与现有的两个拷贝构造函数参数类型都无法匹配,也无法转换成该种参数类型

3) 发现辅助的拷贝构造函数auto_ptr(auto_ptr_ref<T> rhs) throw()

4) 试图将auto_ptr<int>(new int(0))转换成auto_ptr_ref<T>

5) 发现类型转换函数operator auto_ptr_ref<Y>() throw(), 转换成功,从而拷贝成功。 从而通过一个间接类成功的实现了拷贝构造右值(临时对象)

同时,这个辅助方法不会使const auto_ptr被拷贝, 原因是在第5步, 此类型转换函数为non-const的,我们知道,const对象是无法调用non-const成员的, 所以转换失败。当然, 这里有一个问题要注意, 假设你把这些辅助转换的代码注释掉,该行代码还是可能成功编译,这是为什么呢?debug一下, 我们可以发现只调用了一次构造函数,而拷贝构造函数并没有被调用,原因在于编译器将代码优化掉了。这种类型优化叫做returned value optimization,它可以有效防止一些无意义的临时对象的构造。当然,前提是你的编译器要支持returned value optimization。

智能指针,auto_prt的基本用法,及其示例

auto_ptr的使用及其源码注释

/**

Module Name:main.cpp

Description:

继承标准异常类创建了自己的异常类CatException

创建一个类Cat

使用了auto_ptr模板

Author:Harite.K@

Created:MinGW32_3.4.4 && C-Free4.0beta1

Last Change:2006/09/17

*/

#include <memory>

#include <string>

#include <cstring>

#include <iostream>

#include <exception>

#include <cstdio>

using namespace std;

//异常类

class CatException : public exception

{

public:

CatException(const string& errormsg) throw()

{

__errormsg = errormsg;

}

~CatException() throw()

{

}

const char* what() const throw()

{

return __errormsg.c_str();

智能指针,auto_prt的基本用法,及其示例

private:

string __errormsg;

};

//

// 一个简单的自定义类型Cat

//

class Cat

{

public:

//构造

Cat (const string &name, int age)

{

if (name.empty())

{

throw CatException("Exception:猫咪姓名不能为空!");

}

if (age<=0)

{

throw CatException("Exception:猫咪年龄只能为正!");

}

__age = age;

__name = new char[name.length()+1];

strcpy(__name, name.c_str());

#ifdef NEEDDEBUG

cout << "DEBUG:猫咪已被成功创建(年龄:" << __age << ",名字:" << __name << ")" << endl;

#endif

}

Cat (const char *name, int age)

{

if (strlen(name)==0)

{

throw CatException("Exception:猫咪姓名不能为空!");

}

if (age<=0)

{

throw CatException("Exception:猫咪年龄只能为正!");

}

智能指针,auto_prt的基本用法,及其示例

__age = age;

__name = new char[strlen(name)+1];

strcpy(__name, name);

#ifdef NEEDDEBUG

cout << "DEBUG:猫咪已被成功创建(年龄:" << __age << ",名字:" << __name << ")" << endl;

#endif

}

Cat (const Cat& ocat)

{

delete [] __name;

__name = new char[strlen(ocat.__name)+1];

strcpy(__name, ocat.__name);

#ifdef NEEDDEBUG

cout << "DEBUG:猫咪已被成功创建(年龄:" << __age << ",名字:" << __name << ")" << endl;

#endif

}

//方法

void Beep() const

{

cout << "猫咪" << __name << "叫了。" << endl;

}

int GetAge() const

{

return __age;

}

const

char *GetName() const

{

return __name;

}

void SetName(string &name)

{

if (name.empty())

{

智能指针,auto_prt的基本用法,及其示例

throw CatException("Exception:猫咪姓名不能为空!");

}

strcpy(__name, name.c_str());

}

void SetName(char *name)

{

if (strlen(name)==0)

{

throw CatException("Exception:猫咪姓名不能为空!");

}

strcpy(__name, name);

}

void SetAge(int age)

{

if (age<=0)

{

throw CatException("Exception:猫咪年龄只能为正!");

}

__age = age;

}

//重载操作符

Cat &operator=(const Cat& ocat)

{

delete [] __name;

__name = new char[strlen(ocat.__name)+1];

strcpy(__name, ocat.__name);

return *this;

#ifdef NEEDDEBUG

cout << "DEBUG:猫咪已被成功创建(年龄:" << __age << ",名字:" << __name << ")" << endl;

#endif

}

//析构

~Cat ()

{

#ifdef NEEDDEBUG

cout << "DEBUG:猫咪将被删除(年龄:" << __age << ",名字:" << __name << ")" << endl; #endif

delete [] __name;

智能指针,auto_prt的基本用法,及其示例

}

private:

int __age;

char *__name;

};

int main(int argc, char *argv[])

{

try

{

auto_ptr<Cat> mycatAptr(new Cat("Bother", 2));

//此时au_mycat_a便俘获了指向新建Cat对象的指针

//可以使用au_mycat_a来对对象进行操作了,比如“叫”

mycatAptr->Beep();

auto_ptr<Cat> mycatBptr;

mycatBptr = mycatAptr;

//此时,mycatAptr把控制权交给了mycatBptr

mycatBptr->Beep();

//此时如果再次调用au_mycat_a->Beep()就会产生错误。

mycatAptr.reset(mycatBptr.release());

//mycatAptr重启掌控对象,mycatBptr转让了对对象的控制

mycatAptr->Beep();

//此时如果mycatBptr->Beep()将会出错。

Cat *tmp = mycatAptr.release();

//mycatAptr也拒绝了对对象的控制,release()返回指向被放弃的对象的指针(对象并没有被删除)

tmp->Beep();

//此时mycatAptr已经不能在调用Beep()函数了。

}

catch (CatException &ce)

{

cout << ce.what() << endl;

}

return 0;

}

这是在mingw32-3.4.4下找到的源文件。

智能指针,auto_prt的基本用法,及其示例

它被包含在std空间内。

//这个模板结构是为了让auto_ptr成为函数返回值

template<typename _Tp1>

struct auto_ptr_ref

{

_Tp1* _M_ptr;

explicit

auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }

};

template<typename _Tp>

class auto_ptr

{

private:

_Tp* _M_ptr;//真正存储的数据,一个指针型

public:

typedef _Tp element_type;//数据类型

//这种构造直接将指针赋值给_M_ptr

explicit

auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }

//拷贝构造函数,会先将参数__a作废,然后使对象捕获__a原本指向的目标 auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }

//带模板的转化函数,同上。

template<typename _Tp1>

auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { }

//这个=号赋值也会使__a作废,原因在此,注意。

auto_ptr&

operator=(auto_ptr& __a) throw()

{

reset(__a.release());

return *this;

}

//模板例程 ,效果同上。

template<typename _Tp1>

auto_ptr&

operator=(auto_ptr<_Tp1>& __a) throw()

智能指针,auto_prt的基本用法,及其示例

{

reset(__a.release());

return *this;

}

//当auto_ptr对象被销毁时,他无情的销毁了_M_ptr

~auto_ptr() { delete _M_ptr; }

//此处要注意防止_M_ptr为空!

element_type&

operator*() const throw()

{

_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);

return *_M_ptr;

}

//同上

element_type*

operator->() const throw()

{

_GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);

return _M_ptr;

}

//返回_M_ptr,指针类型

element_type*

get() const throw() { return _M_ptr; }

//将对象对_M_ptr的控制拿掉,返回另一个对它的指向

element_type*

release() throw()

{

element_type* __tmp = _M_ptr;

_M_ptr = 0;//置0

return __tmp;

}

//如果__p和_M_ptr不一样的话,删除原_M_ptr内容,然后让其指向__p void

reset(element_type* __p = 0) throw()

{

if (__p != _M_ptr)

{

delete _M_ptr;

智能指针,auto_prt的基本用法,及其示例

_M_ptr = __p;

}

}

//为了匹配类似形式

//auto_ptr<Derived> func_returning_auto_ptr(.....); auto_ptr(auto_ptr_ref<element_type> __ref) throw() : _M_ptr(__ref._M_ptr) { }

//为了匹配类似形式

//auto_ptr<Base> ptr = func_returning_auto_ptr(.....); auto_ptr&

operator=(auto_ptr_ref<element_type> __ref) throw() {

if (__ref._M_ptr != this->get())

{

delete _M_ptr;

_M_ptr = __ref._M_ptr;

}

return *this;

}

//匹配两种转化形式

template<typename _Tp1>

operator auto_ptr_ref<_Tp1>() throw()

{ return auto_ptr_ref<_Tp1>(this->release()); }

template<typename _Tp1>

operator auto_ptr<_Tp1>() throw()

{ return auto_ptr<_Tp1>(this->release()); }

本文来源:https://www.bwwdw.com/article/ws8e.html

Top