`
jarfield
  • 浏览: 202478 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

为什么需要auto_ptr_ref

    博客分类:
  • C++
阅读更多

这几天开始拜读侯捷先生和孟岩先生的译作《C++标准程序库:自修教程与参考手册》 。两位先生确实译功上乘,读得很顺。但是读到P55页关于auto_ptr_ref的讨论,却百思不得其解:为什么需要引入auto_ptr_ref这个辅助类呢?

 

从书中描述来看,仿佛与拷贝构造函数右值类型转换 有关。于是,结合auto_ptr的源代码,google之、baidu之,找了一推资料,终于初步 搞清该问题。

 

auto_ptr的拥有权

C++常见的智能指针有std::auto_ptr、boost::shared_ptr、boost::scoped_ptr、boost::shared_array、boost::scoped_array等。auto_ptr只是其中一种而已。但是,为什么auto_ptr才有auto_ptr_ref ,而boost::shared_ptr却没有shared_ptr_ref呢?

 

答案与auto_ptr的特性有关。auto_ptr强调对资源的拥有权 (ownership)。也就是说,auto_ptr是"它所指对象"的拥有者。而一个对象只能属于一个拥有者,严禁一物二主,否则就是重婚罪,意料外的灾难将随之而来。

 

为了保证auto_ptr的拥有权唯一,auto_ptr的拷贝构造函数和赋值操作符做了这样一件事情移除另一个auto_ptr的拥有权 。为了说明拥有权的转移 ,请看下面的代码示例:

#include <iostream>
#include <memory>
using namespace std;

int main(int argc, char **argv){
	auto_ptr<int> ptr1(new int(1));
	auto_ptr<int> ptr2(ptr1);	//ptr1的拥有权被转移到ptr2

	auto_ptr<int> ptr3(NULL);
	ptr3 = ptr2; 				//ptr2的拥有权被转移到ptr3

	cout<<ptr1.get()<<endl; 	//结果为0
	cout<<ptr2.get()<<endl; 	//结果为0
	cout<<*ptr3<<endl;      	//结果为1

 

auto_ptr的拷贝构造函数与赋值操作符  

由于需要实现拥有权的转移,auto_ptr的拷贝构造函数和赋值操作符,与一般类的做法不太相同。我们可以看看MinGW 5.1.6实现的auto_ptr源代码:

 /**
 *  @brief  An %auto_ptr can be constructed from another %auto_ptr.
 *  @param  a  Another %auto_ptr of the same type.
 *
 *  This object now @e owns the object previously owned by @a a,
 *  which has given up ownsership.
 */
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) {}

/**
 *  @brief  %auto_ptr assignment operator.
 *  @param  a  Another %auto_ptr of the same type.
 *
 *  This object now @e owns the object previously owned by @a a,
 *  which has given up ownsership.  The object that this one @e
 *  used to own and track has been deleted.
 */
auto_ptr&
operator=(auto_ptr& __a) throw () {
	reset(__a.release());
	return *this;
}

 

    可以看到,auto_ptr的拷贝构造函数、赋值操作符,它们的参数都是auto_ptr& ,而不是auto_ptr const &

 

    一般来说,类的拷贝构造函数和赋值操作符的参数都是const &。但是auto_ptr的做法也是合理的:确保拥有权能够转移

 

    如果auto_ptr的拷贝构造函数和赋值操作符的参数是auto_ptr const & ,那么实参的拥有权将不能转移。因为转移拥有权需要修改auto_ptr的成员变量,而实参确是一个const对象,不允许修改。

 

右值与const &

假设我们想写出下面的代码:

#include <iostream>
#include <memory>
using namespace std;

int main(int argc, char **argv) {
	auto_ptr<int> ptr1(auto_ptr<int>(new int(1)));  //使用临时对象进行拷贝构造
	auto_ptr<int> ptr2(NULL);
	ptr2 = (auto_ptr<int>(new int(2)));  			//使用临时对象进行赋值
}

 

    假设没有定义auto_ptr_ref类及相关的函数,那么这段代码将不能通过编译。主要的原因是,拷贝构造函数及赋值操作符的参数:auto_ptr<int>(new int(1)) auto_ptr<int>(new int(2)) 都是临时对象 。临时对象属于典型的右值 ,而非const &是不能指向右值的 (参见More Effective C++ ,Item 19)。auto_ptr的拷贝构造函数及赋值操作符的参数类型恰恰是auto_ptr&,明显 非const &。

 

    同理,下面的两段代码,也不会通过编译:

#include <iostream>
#include <memory>
using namespace std;
auto_ptr<int> f();
int main(int argc, char **argv) {
	auto_ptr<int> ptr3(f());  //使用临时对象进行拷贝构造
	auto_ptr<int> ptr4(NULL);
	ptr4 = f();  			  //使用临时对象进行赋值
}
#include <iostream>
#include <memory>
using namespace std;
auto_ptr<int> f(){
	return auto_ptr<int>(new int(3));  //这里其实也使用临时对象进行拷贝构造
}

 

    普通类不会遇到这个问题,是因为他们的拷贝构造函数及赋值操作符(不管是用户定义还是编译器生成的版本),参数都是const &。

 

auto_ptr_ref之目的

传说当年C++标准委员会的好多国家,因为这个问题都想把auto_ptr从标准库中剔除。好在Bill Gibbons和Greg Colvin创造性地提出了auto_ptr_ref,解决了这一问题,世界清静了。

 

auto_ptr_ref之原理

    很显然,下面的构造函数,是可以接收auto_ptr临时对象的。

auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }

 

    但另一个问题也很显然:上述构造函数不能通过编译。如果能通过编译,就会陷入循环调用。我们稍作修改:

auto_ptr(auto_ptr_ref<element_type> __ref) throw()  //element_type就是auto_ptr的模板参数。
      : _M_ptr(__ref._M_ptr) { } 

 

    该版本的构造函数,可以接收auto_ptr_ref的临时对象。如果auto_ptr可以隐式转换到auto_ptr_ref,那么我们就能够用auto_ptr临时对象来调用该构造函数。这个隐式转换不难实现:

template<typename _Tp1>
operator auto_ptr_ref<_Tp1>() throw(){ 
    return auto_ptr_ref<_Tp1>(this->release()); 
}

 

    至此,我们可以写出下面的代码,并可以通过编译:

#include <iostream>
#include <memory>
using namespace std;

int main(int argc, char **argv) {
    auto_ptr<int> ptr1(auto_ptr<int>(new int(1)));  //调用auto_ptr_ref版本的构造函数
}

 

   同理,如果我们再提供下面的函数:

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;
}

 

    那么,下面的代码也可以通过编译:

#include <iostream>
#include <memory>
using namespace std;

int main(int argc, char **argv) {
    auto_ptr<int> ptr2(NULL);
    ptr2 = (auto_ptr<int>(new int(2)));  //调用auto_ptr_ref版本的赋值操作符
}

 

auto_ptr_ref之本质

本质上,auto_ptr_ref赋予了auto_ptr“引用”的语义,这一点可以从auto_ptr_ref的注释看出:

/**
  *  A wrapper class to provide auto_ptr with reference semantics.
  *  For example, an auto_ptr can be assigned (or constructed from)
  *  the result of a function which returns an auto_ptr by value.
  *
  *  All the auto_ptr_ref stuff should happen behind the scenes.
  */
template<typename _Tp1>
struct auto_ptr_ref
{
    _Tp1* _M_ptr;
      
    explicit auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
};

 

auto_ptr_ref之代码

这里列出auto_ptr_ref相关的函数,供参考:

auto_ptr(auto_ptr_ref<element_type> __ref) throw()
: _M_ptr(__ref._M_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());
}

 参考资料

auto_ptr之变迁

auto_ptr实现之我见

auto_ptr_ref的奇妙(上)

auto_ptr_ref的奇妙(下)

auto_ptr_ref 的目的是什么

关于auto_ptr_ref的一点问题

左值和右值

分享到:
评论
4 楼 mrvon 2012-10-30  
很感谢你费心总结的文章,正读到auto_ptr源码,总于解了疑惑.
3 楼 myan 2010-09-04  
写得挺好。

不过 auto_ptr 已经被废弃了。现在用 unique_ptr。
2 楼 jarfield 2010-09-03  
ray_linn 写道
呵呵,你的参考里说的,事实上左值和右值是被误译了

L = Location 可寻址
R = Read 可读


呵呵,多谢指出。
“左值”和“右值”的说法,从字面上理解,确实不准确。不过大家都这么叫了。
“左值和右值”这篇参考文章中,其实也指出了L和R的确切含义。
1 楼 ray_linn 2010-09-03  
呵呵,你的参考里说的,事实上左值和右值是被误译了

L = Location 可寻址
R = Read 可读

相关推荐

    智能指针的介绍和用法

    C++标准库中提供了几种智能指针类型,如std::auto_ptr、std::shared_ptr、std::unique_ptr等,而Boost库也提供了智能指针,例如boost::shared_ptr、boost::scoped_ptr等。除了标准库和第三方库提供的智能指针,不同...

    Lua-C 绑定库LuatinkerE.zip

    使用大量C 14特性Variadic Template 和 index_sequence, SFINAE enable_if 和 type_traits, tuple, function, forward_ref, decltype(auto)。通过vc2015,gcc5.0,clang3.6编译new feature 新特性一览:导入lua的函数...

    疯狂VC技巧集.rar

    此外,内存管理是C++中的核心概念,VC++提供了智能指针如auto_ptr、unique_ptr、shared_ptr等,它们可以自动管理对象的生命周期,防止内存泄漏。了解这些工具的使用和原理,对于编写高效且安全的代码至关重要。 ...

    The Visual C++ Language

    `Dispose`模式和智能指针(如`auto_ptr`和`unique_ptr`)确保资源在不再需要时得到正确释放。 9. **设计模式**: - C++/CLI可以实现多种设计模式,如工厂模式、单例模式和观察者模式,这些模式在.NET开发中至关...

    C++11 语法记录

    9. **智能指针(Smart Pointers)**:`std::unique_ptr`, `std::shared_ptr`和`std::weak_ptr`是C++11新增的智能指针,它们自动管理对象的生命周期,防止内存泄漏。 10. **移动构造函数与移动赋值运算符(Move ...

    Effictive STL CHM中文版

    条款8: 千万不要把auto_ptr放入容器中 条款9: 小心选择删除选项 条款10: 当心allocator的协定和约束 条款11: 了解自定义allocator的正统使用法 条款12: 对STL容器的线程安全性的期待现实一些 vector和string ...

    C++/CLI课件

    - **智能指针`auto_ptr`**:虽然在C++11中已被弃用,但在C++/CLI中,`auto_ptr`依然用于管理非托管资源,当不再需要时会自动删除对象。 - **`HandleRef`与`SafeHandle`**:这些类用于安全地管理.NET托管对象的引用...

    c++教程-第 1 章 迈向现代 C.docx

    - **`auto_ptr`** 被弃用: - 替代方案: 使用 `unique_ptr`。 - **`register` 关键字** 被弃用: - 仍然可以使用,但不再具有任何实际意义。 - **`bool` 类型的 `++` 操作** 被弃用: - 通常情况下,不应直接对 `...

    LuaBind 源码 (Lua增强库)

    LuaBind需要Boost 1.32.0 或者 1.33.0 (只需要头文件即可). LuaBind还需要Lua. 官方的构建LuaBind的方式是通过 Boost.Build V2 . 为此,你需要设置两个环境变量: BOOST_ROOT 指向你的Boost安装目录 LUA_PATH 指向你...

    Effective STL(中文)

    永不建立auto_ptr的容器 条款9:在删除选项中仔细选择 条款10:注意分配器的协定和约束 条款11:理解自定义分配器的正确用法 条款12:对STL容器线程安全性的期待现实一些 vector和string 条款13:...

    effective stl stl 技巧

    - **核心概念**:`ptr_fun`、`mem_fun`和`mem_fun_ref`提供了一种将函数或成员函数包装为仿函数的方式。 - **应用场景**: - 当需要将普通函数或成员函数转换为仿函数时,使用这些工具。 #### 条款42:确定less...

    VisualC++(VC++)编程序软件语言关键字大全集合.pdf

    `namespace`,`new`,`noexcept`,`noinline`,`noreturn`,`novtable`,`nullptr`,`operator`,`private`,`property`,`protected`,`public`,`ref class`,`ref struct`,`register`,`reinterpret_cast`,`...

    VisualC++(VC++)编程序软件语言关键字大全集合参考.pdf

    ptr`、`literal`、`long`、`mutable`、`namespace`、`new`、`noexcept`、`noinline`、`noreturn`、`novtable`、`nullptr`、`operator`、`private`、`property`、`protected`、`public`、`ref class`、`ref struct`、...

    Visual C++ 2005从入门到精通(普及版)ch5

    7. **资源管理**:托管C++提供了智能指针(如`auto_ptr`)和`SafeHandle`类来帮助管理非托管资源,确保在不再需要时正确释放。 8. **代码示例**:书中可能包含多个实际编程示例,覆盖了从简单的数据类型操作到复杂...

    初学c++的40条代码

    26. 智能指针:auto_ptr、unique_ptr、shared_ptr等智能指针管理动态内存,避免内存泄漏。 27. 异常处理:try、catch和throw用于异常处理,确保程序在出现错误时能优雅地终止。 28. 流式输出:使用`运算符可以实现...

    C++ 备忘单-.pdf

    - 引用:`int& ref = variable;` 9. **输入输出**: - 输入:`std::cin` - 输出:`std::cout` 10. **字符串处理**: - 字符串操作:`std::string` 类成员函数如 `append`, `substr`, `find` 11. **异常处理**...

    C++Primer+浓缩.pdf

    - **动态内存分配**:通过`new`关键字在运行时动态申请内存,需要手动释放(`delete`)。这种方式更加灵活,但也更容易出错。 #### 四、类的构造函数 - **默认构造函数**:如果一个类没有显式定义任何构造函数,则...

Global site tag (gtag.js) - Google Analytics