前置描述
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.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也将会容易出现段错误的情形。之后再做详细展开。但是,只有知道其中的不足,才能避开不可以使用的情况。