C++中的auto_ptr (修改版本)


stl <memory> 文件中的 std::auto_ptr 在C++中的故事特别多, 在它的演变过程中至少出现了3个版本.
http://www.josuttis.com/libbook/auto_ptr.html 这个连接里面有它完整的故事.
VC6中STL带的auto_ptr( 带owner字段)的版本应该就是文中说的Version 2.

最新的Version里面包含了一个auto_ptr_ref, 这个是当将auto_ptr作为函数返回值和函数参数时需要引入的
一个"额外间接层".

下面是它的一些说明:

auto_ptr的拷贝构造函数和一般我们常见的不同, 它的参数rhs并不是const reference, 而是refence,

auto_ptr( /*const */ auto_ptr& rhs)
{
  ...
}

假设我们需要将一个auto_ptr作为某个函数的返回值, 例如
auto_ptr<int> source()
{
  return auto_ptr<int>(new int(3));
}

那么我们如何在caller中得到返回的结果呢?
理所当然的语法是:
auto_ptr<int> p( source() );     (拷贝构造函数)
或者
auto_ptr<int> p = source();      (拷贝构造函数)
或者
auto_ptr<int> p = ...;           (operator=)
p = source();


但是如果没有auto_ptr_ref的存在, 上面这些行实际上应该是一个编译错误(VC6不报错), 原因是:

C++中有左值/右值之分, 函数如果返回值, 那么是r-value. 右值作为reference函数参数时, 只能是const reference.
因此source函数返回的auto_ptr作为rhs实参调用auto_ptr的拷贝构造函数时, 只能是const refernce, 但是
这个函数的签名需要rhs为reference, 因此无法编译.
举个最简单的例子:
有函数:
int foo() { return 0; }
void bar(int & i) { }
调用
int& i  = foo() ;       //错误
const int& i = foo();   //OK
bar(foo())              //错误

同理, 拷贝构造函数不过是一个特殊的"函数"而已, 我们上面的source函数返回的auto_ptr对象也只能作为一个
const auto_ptr&, 但是这个拷贝构造函数需要的参数原型是auto_ptr&, 而不是const auto_ptr& .
因此auto_ptr引入了一个'额外的间接层' auto_ptr_ref, 来完成一个从r-value到l-value之间的过渡.

基本的思路是;
提供另外一个构造函数, 接受一个以值传递的auto_ptr_ref:
auto_ptr( auto_ptr_ref ref)
{
      ....
}

然后在auto_ptr类中, 提供一个自动转型的函数
operator auto_ptr_ref ()
{
    .....
}


这样, source返回一个auto_ptr, 编译器尝试调用拷贝构造函数, 发现参数不必配(期望const), 然后发现了一个自动转型的
operator auto_ptr_ref()函数, 而后又发现通过调用该自动转型得到一个auto_ptr_ref对象后, 可以调用caller的
auto_ptr的以auto_ptr_ref为参数的非explicit的构造函数, 完成了一个auto_ptr到另外一个auto_ptr之间的复制过程.

注意一点: operator auto_ptr_ref () 不是const成员函数.
C++语法规则中对于临时变量的r-value有个诡秘的地方就是:
如果你需要将r-value保存在一个reference中, 或者作为某个refence的函数参数, 那么必须为const reference,
但是你也可以在一个完整的表达式中直接使用这个临时变量, 这种情况下该临时变量实际上并不是作为const reference对待,
因为你可以调用它的非const成员函数. 例如:

class Integer
{
public:

  void zero() { i = 0; }

private:
  int i;
};

class Integer
{
public:

  void zero() { i = 0; }

private:
  int i;
};

Integer().zero();       //创建一个Integer的临时变量, 然后调用非const成员函数.

这样, auto_ptr<int> p( source() );

实际上发生的事情就是:
 auto_ptr<int> p( source().operator auto_ptr_ref());

通过source()函数得到一个临时的auto_ptr对象, 然后调用其中的自动转换函数得到一个auto_ptr_ref,
即使该转型函数是非const成员函数仍然可行.

然后调用 p 对象的以auto_ptr_ref为参数的构造函数进行复制.
然后, source()函数创建的临时对象在整个表达式结束后被析构, 当然这个时候这个临时对象内部的指针已经被reset(0)了,
因为该指针的拥有权已经被p接管了. (否则会重复删除)

std::auto_ptr在很多情况下是很便利的, 例如一个函数内部需要通过new分配一个结构, 那么谁来释放是一个问题,
一种原则是谁分配, 谁释放, 但是对于这种情况显然不合适.
利用auto_ptr就简单得多了: 谁拥有谁释放, 谁都不要那么就编译器自动释放它. 例如

有个函数:
auto_ptr<sth> create_sth()
{
  auto_ptr<sth> p(new sth);
  return p;
}

调用1:
auto_ptr<sth> p = create_sth();
...
p退出作用域, 自动释放.

调用2:
create_sth();
没有人接受这个返回的对象, 那么编译器自动会调用auto_ptr的析构函数释放之.

sink也是一个作用:
例如我已经拥有一个auto_ptr<sth>对象的指针, 那么我可以定义一个sink函数, 原型如下:
void sink(auto_ptr<sth> p)
{
}

正如sink名字暗示的一样, sink函数起到一个吸收作用, 将某个外部的auto_ptr对象吸收过来,类似于宇宙中的"黑洞".
例如:
  auto_ptr<sth> p(new sth);
  sink(p);
  //这里, p指向null了, p所指的sth对象已经被sink函数"吸收"了.

当然为了防止这种情况在你不注意的情况下发生, 你可以
const auto_ptr<sth> p(new sth);
sink(p); //编译错误
auto_ptr<sth> p2 = p;        //编译错误

这样, 一个const auto_ptr<sth>的对象一旦构造完成, 永远不会失去对该对象的拥有权. "一旦拥有, 从不失去".

当然auto_ptr最重要的作用, 也是它的原始目的是为了提供异常安全.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
C++智能指针:auto_ptr详解 指针,相信⼤家并不陌⽣。⽆论是我们在进⾏查看内存还是在修改字符串,我们都会⽤到指针。 最常见的情况则是我们使⽤malloc或者new申请到了⼀块内存,然后⽤⼀个指针来保存起来。我们都知道有内存的申请那就必须要对它进⾏ 释放的处理,否则会造成最严重的后果——内存泄漏。⼀个或者两个申请的内存我们或许不会忘记去释放,但是如果成千上万⾏代码,你还 能记得住哪个释放了哪个没有释放吗? ⽽智能指针就是为了解决这个问题⽽产⽣的。最开始智能指针是在boost库的,随着时间发展现在已经成为了C11的特性。(虽然我们本 篇要介绍的最基础的auto_ptr在C++11已经被unique_ptr替代了。。) 智能指针的基本原理 智能指针其实是⼀个类,可以通过将普通指针作为参数传⼊智能指针的构造函数实现绑定。只不过通过运算符重载让它"假装"是⼀个指 针,也可以进⾏解引⽤等操作。既然智能指针是⼀个类,对象都存在于栈上,那么创建出来的对象在出作⽤域的时候(函数或者程序结束)会 ⾃⼰消亡,所以在这个类的析构函数写上delete就可以完成智能的内存回收。 Auto_ptr详解 使⽤时,需要包含头⽂件:memory。 auto_ptr,作为智能指针的始祖,能基本实现我们所期望的功能。⽽且设计简单源码易懂,虽然缺陷众多,但作为了解智能指针的研究对象 还是⼗分合适的。 ⾸先我们先来写⼀个测试类⽤于分析。 #include <iostream> #include <memory> using namespace std; class Test { public: Test(int param = 0) //调⽤时,可以指定是第⼏个对象。默认第0个 { num = param; cout << "Simple:" << num << endl; } ~Test() //析构时会指出第⼏个对象被析构。 { cout << "~Simple:" << num << endl; } void PrintSomething() //输出附加的字符串⽅法。 { cout << "PrintSomething:" << info_extend.c_str() << endl; } int num; //代表这个对象的序号。 string info_extend; //附加的字符串。 }; 接下来,我们通过⼏个测试函数来实验⼀下auto_ptr的基本功能,再来了解⼀下它的弊端。 I.Auto_ptr的基本功能 的基本功能 void TestAutoPtr1() { auto_ptr<Test> my_auto (new Test(1)); //绑定⼀个Test类的新建对象 if(my_auto.get()) //get函数⽤来显式返回它拥有的对象指针,此处判⾮空 { my_auto->PrintSomething(); my_auto.get()->info_extend = "Addition"; //对类内成员进⾏操作 my_auto->PrintSomething(); //看是否成功操作 (*my_auto).info_extend += " other"; //实验解引⽤操作 mt_auto->PrintSomething(); } } 运⾏结果: 我们可以看到在绑定时输出Simple:1,之后也能正常实现Test类的功能,同时my_auto可以通过get⽅法进⾏裸指针赋值以及使⽤*进⾏ 解引⽤操作,与普通指针⽆异。最后函数结束后,在调⽤析构函数的同时auto_ptr的析构函数也将Test类的对象delete掉,我加⼊的内存泄 漏探测⼯具也显⽰No memory leaks detected(没有检查测到内存泄漏). II.Auto_ptr的弊端 的弊端1 void TestAutoPtr2() { auto_ptr<Test> my_auto1 (new Test(1)); if(my_auto1.get()) { (*my_auto1).info_extend = "Test"; //先赋予⼀个初始值作为区别两个指针的标志 auto_ptr<Test> my_auto2; my_auto2 = my_auto1; my_auto2->PrintSomething(); //打印发现成功转移 my_auto1->PringSomething(); //崩溃 } } 在这个程序的最后⼀步会崩溃,罪魁祸⾸就是my_auto2 = my_auto1语句。 autp_ptr对赋值运算符重载的实现是reset(Myptr.release()),即reset和release函数的组合。release会释放所有权,reset则是⽤于接受

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值