【STL源码剖析:auto_ptr】

前置描述

auto_ptr在C++11中已经被过滤,属于被废弃的智能指针。本篇的出现主要为了通过auto_ptr的简单实现代码,快速熟悉STL代码风格。同时探索该智能指针被C++11过滤的原因。

如何找到auto_ptr

auto_ptr源码路径

4.8.2/backward/auto_ptr.h

按照标准路径,若需要使用auto_ptr,需要在代码中加入下述内容:

#include <memory>

该头文件具体内容如下:

/** @file include/memory
 *  This is a Standard C++ Library header.
 */

#ifndef _GLIBCXX_MEMORY
#define _GLIBCXX_MEMORY 1

#pragma GCC system_header

#include <bits/stl_algobase.h>
#include <bits/allocator.h>
#include <bits/stl_construct.h>
#include <bits/stl_uninitialized.h>
#include <bits/stl_tempbuf.h>
#include <bits/stl_raw_storage_iter.h>

#if __cplusplus >= 201103L
#  include <exception>        	  // std::exception
#  include <typeinfo>         	  // std::type_info in get_deleter
#  include <iosfwd>           	  // std::basic_ostream
#  include <ext/atomicity.h>
#  include <ext/concurrence.h>
#  include <bits/functexcept.h>
#  include <bits/stl_function.h>  // std::less
#  include <bits/uses_allocator.h>
#  include <type_traits>
#  include <functional>
#  include <debug/debug.h>
#  include <bits/unique_ptr.h>
#  include <bits/shared_ptr.h>
#  if _GLIBCXX_USE_DEPRECATED
#    include <backward/auto_ptr.h>
#  endif
#else
#  include <backward/auto_ptr.h>
#endif

#endif /* _GLIBCXX_MEMORY */

可以看到auto_ptr被单独放在预处理命令逻辑的#else中,与shared_ptr、unique_ptr、weak_ptr(该类定义在share_ptr的相关文件中)隔离。
其中__cplusplus >= 201103L代表C++当前的版本不能小于C++11。(201103L代表C++11)。
意思是,如果代码中使用的STL版本为C++11及以上版本,将不再对外提供auto_ptr的实现。

源码

#ifndef _BACKWARD_AUTO_PTR_H
#define _BACKWARD_AUTO_PTR_H 1

#include <bits/c++config.h>
#include <debug/debug.h>

// 所在命名空间为std::,且本文件当中的函数将在符号表中生成symbol
namespace std _GLIBCXX_VISIBILITY(default)
{
//命名空间开始标志,是一个宏,代表的是空内容,仅仅作为标识
  _GLIBCXX_BEGIN_NAMESPACE_VERSION

//适配器!?
  template<typename _Tp1>
    struct auto_ptr_ref
    {
      _Tp1* _M_ptr;
      
      explicit
      auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
    } _GLIBCXX_DEPRECATED;
	
//智能指针模板类
  template<typename _Tp>
    class auto_ptr
    {
    private:
      _Tp* _M_ptr;  // 以私有权限存储一个模板参数类型指针
      
    public:
      /// The pointed-to type.
      typedef _Tp element_type;
      
      // explicit避免隐式转换,例如通过int构造自定义类
      //此处不得不提operator
      /*
	  	众所周知,operator大多数时间用来重载运算符,但也可以用于自定义隐式转换
	  	如:
	  	class my_example 
	  	{
	  	  public:
	  	  	my_example():x(0) { }
	  	  	
	  		operator int() {
	  			return x;
	  		};
	  	  private:
	  	    int x;	
	  	};
	  */
	  // 此处将对入参进行严格的类型检查,不接受隐式转换
      explicit
      auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }
      
      // throw() 为异常规范,实际项目一般不使用。此处代表该函数将不抛出任何异常,即使抛出,将不受try,catch代码块的检测
      auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) { }
     
     // 当使用智能指针的引用作为构造函数的参数时,传入的智能指针将放弃保存的指针,移交给当前的智能指针。
     //因此后续访问入参的指针所指向的内容将会出现段错误,因为该指针已经被重置为空指针。 下面的函数都具备该特性。
      template<typename _Tp1>
        auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { }
        
      auto_ptr&
      operator=(auto_ptr& __a) throw()
      {
		reset(__a.release());
		return *this;
      }
      
      template<typename _Tp1>
        auto_ptr&
        operator=(auto_ptr<_Tp1>& __a) throw()
        {
		  reset(__a.release());
		  return *this;
		}
	  
	  // 析构时直接delete存储指针
	  // 但是,如果该指针析构之前已经移交权限,指针内容将会被置为0,delete一个空指针,或许是废弃的原因?
	  //可见单纯的使用RAII的方式构建智能智能并不能做到太优雅。
      ~auto_ptr() { delete _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; 
      }
      /*******************************************************************/
      
      element_type*
      get() const throw() { return _M_ptr; }
      
      // 释放指针权限
      element_type*
      release() throw()
      {
		element_type* __tmp = _M_ptr;
		_M_ptr = 0;
		return __tmp;
      }
      
      // 重置指针所管理的内容
      // 此处会delete管理的上一个指针
      void
      reset(element_type* __p = 0) throw()
      {
		if (__p != _M_ptr)
		  {
		    delete _M_ptr;
		    _M_ptr = __p;
		  }
      }
      
      //此处为与auto_ptr类上面定义的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()); }
    } _GLIBCXX_DEPRECATED;


  // _GLIBCXX_RESOLVE_LIB_DEFECTS
  // 541. shared_ptr template assignment and void
  // 空类保护
  // 如果模板参数为void,将会构建一个空类
  // PS:为了区别空类和空类的实例化对象,空类的实例化对象所占空间大小一般为1字节
  template<>
    class auto_ptr<void>
    {
    public:
      typedef void element_type;
    } _GLIBCXX_DEPRECATED;

// 此处为与C++11智能指针的兼容转换,由于在C++11中已经过滤该指针,因此不做详细的阐述
#if __cplusplus >= 201103L
	//shared_ptr的计数器实现对auto_ptr类型入参的构造,方便auto_ptr和shared_ptr之间的转换
  template<_Lock_policy _Lp>
  template<typename _Tp>
    inline
    __shared_count<_Lp>::__shared_count(std::auto_ptr<_Tp>&& __r)
    : _M_pi(new _Sp_counted_ptr<_Tp*, _Lp>(__r.get()))
    { __r.release(); }

//shared_ptr的构造
  template<typename _Tp, _Lock_policy _Lp>
  template<typename _Tp1>
    inline
    __shared_ptr<_Tp, _Lp>::__shared_ptr(std::auto_ptr<_Tp1>&& __r)
    : _M_ptr(__r.get()), _M_refcount()
    {
      __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)
      static_assert( sizeof(_Tp1) > 0, "incomplete type" );
      _Tp1* __tmp = __r.get();
      _M_refcount = __shared_count<_Lp>(std::move(__r));
      __enable_shared_from_this_helper(_M_refcount, __tmp, __tmp);
    }

  template<typename _Tp>
  template<typename _Tp1>
    inline
    shared_ptr<_Tp>::shared_ptr(std::auto_ptr<_Tp1>&& __r)
    : __shared_ptr<_Tp>(std::move(__r)) { }

//unique_ptr的构造
  template<typename _Tp, typename _Dp>
  template<typename _Up, typename>
    inline
    unique_ptr<_Tp, _Dp>::unique_ptr(auto_ptr<_Up>&& __u) noexcept
    : _M_t(__u.release(), deleter_type()) { }
#endif

// 命名空间结束标志,宏,为空内容,仅仅作为标识
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

#endif /* _BACKWARD_AUTO_PTR_H */

要点补充

对于

namespace std _GLIBCXX_VISIBILITY(default) {}

// 上述宏的具体实现
#if _GLIBCXX_HAVE_ATTRIBUTE_VISIBILITY
# define _GLIBCXX_VISIBILITY(V) __attribute__ ((__visibility__ (#V)))
#else
// If this is not supplied by the OS-specific or CPU-specific
// headers included below, it will be defined to an empty default.
# define _GLIBCXX_VISIBILITY(V) _GLIBCXX_PSEUDO_VISIBILITY(V)
#endif

一般情况下构建工程,不可避免的要使用到动态库和静态库。
利用c++源码底层的__attribute__机制(attribute机制的详细使用建议必应或者谷歌)。
本次只是介绍__attribute__ ((visibility (default)))以及__attribute__ ((visibility (hidden))的实际区别。
当在命名空间中使用了__attribute__ ((visibility (hidden)),在该头文件中定义,在其他.cpp文件中实现的函数,将无法在生成动态库时加入动态库的符号表。但,比如模板等,在该头文件中实现了具体方法的,将会正常生成符号表。
如下:

#pragma once
#incldue <x86_64-redhat-linux/bits/c++config.h>

namespace MySpace __attribute__ ((__visibility__ (hidden)) 
{
	// 将不会生成符号表中的符号,无法被外部使用(即使用该动态库将无法使用作者实现的该函数),只能被本项目使用
	int func_1();
	template <typename _T>
	int func_2(_T& value) {
		return 1;
	}
}

案例

attribute.h
attribute.h
attribute.cpp
attribute.cpp
上图为两个简单的例子。
func为在个人命名空间中的一个模板函数,它调用了仅在该头文件中做了声明的func2函数,同时func3函数也调用了func2函数。此时选择不在符号表中加入func2函数的符号,将会导致x项目外部引用该头文件的并使用了func2相关函数的代码编译出错,因为从动态库的符号表中找不到该函数。
但如果生成的是静态库,由于静态库是整体编译,并非运行时根据相关symbol去查符号表,不会出现编译报错,函数也能正常使用。
动态库符号表
【上图为生成动态库的符号表】
简要生成命令:

 g++ -fPIC -shared -o libxxx.so

具体情况,自行实践。

总结

使用RAII实现的智能指针。但在C++11中引入了shared_ptr这种计数类型的指针,以及unique_ptr这种独占型的指针,还有为解决shared_ptr循环引用问题而存在的weak_ptr,auto_ptr在日常项目使用还是更加容易写出bug的。但RAII是需要时刻记住的。但并不代表C++11的智能指针是完美的,在某些情况下还是会出现bug,比如利用std::move,用右值引用构造shared_ptr也将会容易出现段错误的情形。之后再做详细展开。但是,只有知道其中的不足,才能避开不可以使用的情况。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值