
先前部分的摘要
由于使用C ++ 11编译器的能力受到限制,并且由于缺乏替代性,boost希望在编译器随附的C ++ 98 / C ++ 03库之上编写自己的标准C ++ 11库实现。
实现了static_assert ,
noexcept ,
countof ,并且在考虑了所有非标准定义和编译器功能之后,出现了有关当前编译器支持的功能的信息。 这样就完成了
core.h的描述,但是如果没有
nullptr ,它将是不完整的。
链接到GitHub,为不耐烦的读者和非读者提供今天的结果:
欢迎有建设性的批评和批评
因此,让我们继续。
目录
引言第1章。Viam supervadet vadens第2章。#ifndef __CPP11_SUPPORT__#定义__COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif第3章。找到理想的nullptr实现第4章C ++模板魔术....
4.1我们从小处着手....
4.2关于日志为我们编译了多少个奇迹般的错误....
4.3指针和所有所有....
4.4模板库还需要什么第五章
...
第3章。找到理想的nullptr实现
在经历了非标准的编译器宏的宏大史诗以及它们所展现的“奇妙”发现之后,我终于可以添加
nullptr了 ,这让我很振奋。 最后,您可以使用0甚至
NULL摆脱所有这些比较。

大多数程序员将
nullptr实现为
#define nullptr 0
这可能已经结束了本章。 如果您自己想要
nullptr ,那么只需将0替换为这样的定义,因为从本质上讲,这是正确操作所需要的。
不要忘记真正写一张支票,否则突然会有其他人发现此定义:
#ifndef nullptr #define nullptr 0 #else #error "nullptr defined already" #endif
预处理程序指令
#error会在编译时
产生人类可读文本错误,是的,这是一个标准指令,很少使用,但是可以找到。
但是在这样的实现中,我们错过了标准中描述的要点之一,即
std :: nullptr_t-一种单独的类型,其常量实例为
nullptr 。 铬开发人员曾经
尝试解决此问题(现在有一个更新的编译器和常规的
nullptr ),将其定义为可以转换为任何类型的指针的类。 由于按标准,由于
nullptr的大小应等于指向
void的指针的大小(并且
void *还应包含任何指针,但指向类成员的指针除外),因此我们通过添加未使用的null指针来“标准化”此实现:
class nullptr_t_as_class_impl { public: nullptr_t_as_class_impl() { } nullptr_t_as_class_impl(int) { }
此类到任何指针的转换归因于该类型的模板运算符,如果将其与
nullptr进行比较,则会调用该运算符。 也就是说,表达式
char * my_pointer; if(my_pointer == nullptr)实际上将转换为
if(my_pointer == nullptr.operator char *()) ,它将指针与0进行比较。需要第二个类型运算符将
nullptr转换为指向类成员的指针。 在这里,Borland C ++ Builder 6.0是“杰出的自己”,他意外地认为这两个运算符是相同的,并且可以轻松地将指向类成员的指针与普通指针相互比较,因此每次将
nullptr与指针(这是一个错误,也许不仅限于此编译器)。 我们正在为这种情况编写一个单独的实现:
class nullptr_t_as_class_impl1 { public: nullptr_t_as_class_impl1() { } nullptr_t_as_class_impl1(int) { }
此
nullptr视图的优点在于,现在为
std :: nullptr_t提供了一个单独的类型。 劣势? 在通过三元运算符进行编译和比较时,
nullptr常量会
丢失 ,编译器无法解析它。
unsigned* case5 = argc > 2 ? (unsigned*)0 : nullptr;
我想“和棋子一起走”。 解决方案只有一个:
enum 。 C ++中的枚举成员将具有自己单独的类型,并且也将毫无问题地转换为
int (实际上,它们是整数常量)。 枚举成员的此属性将为我们提供帮助,因为最常见的
int是使用非常“特殊”的0代替指针的
nullptr 。 我在互联网上还没有看到过
nullptr的这种实现,也许这也很糟糕,但是我不知道为什么。 让我们编写一个实现:
#ifdef NULL #define STDEX_NULL NULL #else #define STDEX_NULL 0 #endif namespace ptrdiff_detail { using namespace std; } template<bool> struct nullptr_t_as_ulong_type { typedef unsigned long type; }; template<> struct nullptr_t_as_ulong_type<false> { typedef unsigned long type; }; template<bool> struct nullptr_t_as_ushort_type { typedef unsigned short type; }; template<> struct nullptr_t_as_ushort_type<false> { typedef nullptr_t_as_long_type<sizeof(unsigned long) == sizeof(void*)>::type type; }; template<bool> struct nullptr_t_as_uint_type { typedef unsigned int type; }; template<> struct nullptr_t_as_uint_type<false> { typedef nullptr_t_as_short_type<sizeof(unsigned short) == sizeof(void*)>::type type; }; typedef nullptr_t_as_uint_type<sizeof(unsigned int) == sizeof(void*)>::type nullptr_t_as_uint; enum nullptr_t_as_enum { _nullptr_val = ptrdiff_detail::ptrdiff_t(STDEX_NULL), _max_nullptr = nullptr_t_as_uint(1) << (CHAR_BIT * sizeof(void*) - 1) }; typedef nullptr_t_as_enum nullptr_t; #define nullptr nullptr_t(STDEX_NULL)
如您所见,这里的代码比仅声明成员
nullptr = 0的 枚举 nullptr_t多了一些代码。 首先,可能没有
NULL定义。 应该在
相当标准的标头列表中定义它,但是如实践所示,最好安全地播放它并检查该宏。 其次,根据实现定义的标准,即C ++,C ++中的
枚举表示形式 枚举类型可以用任何整数类型表示(前提是这些类型不能超过
int ,前提是
枚举值
适合它)。 例如,如果您声明
枚举测试{_1,_2},则编译器可以轻松地将其表示为
short,那么很有可能
sizeof( test ) != Sizeof(void *) 。 为了使
nullptr实现
符合标准,您需要确保编译器为
nullptr_t_as_enum选择的类型的大小与指针的大小相匹配,即 基本上等于
sizeof(void *) 。 为此,请使用
nullptr_t_as ...模板,选择一个等于指针大小的整数类型,然后将枚举中元素的最大值设置为该整数类型的最大值。
我要注意标准climits标头中定义的宏CHAR_BIT 。 此宏设置为一个字符中的位数,即 当前平台上每字节的位数。 一个有用的标准定义,开发人员不应有的通过在所有位置使用八位数字来绕开,尽管在一个字节中的某些地方根本没有8位 。
另一个功能是将
NULL分配为
enum元素的值。 一些编译器会向
NULL分配“非索引器”这一事实发出警告(并且可以理解他们的担忧)。 我们将标准
名称空间取出到本地
ptrdiff_detail中 ,以免使名称空间的其余部分杂乱无章,然后,为了使编译器平静,我们将
NULL显式转换为
std :: ptrdiff_t -C ++中另一种使用不足的类型,用于表示算术运算的结果(减)带有指针,通常是
std :: size_t类型的别名(在C ++ 11中为
std :: intptr_t )。
FINAE
在这里,这是我故事中的第一次,我们在C ++中遇到这样的现象,即
替换失败不是错误(SFINAE) 。 简而言之,其本质在于,当编译器“遍历”特定调用的适当函数重载时,它应检查所有这些重载,并且在第一次失败或第一次适当的重载之后不要停止。 从
歧义出发,来自编译器的观点有两个相同的重载,以及编译器为具有特定参数的特定调用选择最合适的函数重载的能力,由此产生了有关
歧义的信息。 编译器的此功能使您可以对所有模板“魔术”进行最大的
贡献 (通过
std :: enable_if方式 ),并且它也是boost和我的库的基础。
结果,由于有几个
nullptr实现
,因此我们在编译阶段使用SFINAE“选择”最佳方法。 我们声明类型“是”和“否”,以检查下面声明的探测函数的
大小 。
namespace nullptr_detail { typedef char _yes_type; struct _no_type { char padding[8]; }; struct dummy_class {}; _yes_type _is_convertable_to_void_ptr_tester(void*); _no_type _is_convertable_to_void_ptr_tester(...); typedef void(nullptr_detail::dummy_class::*dummy_class_f)(int); typedef int (nullptr_detail::dummy_class::*dummy_class_f_const)(double&) const; _yes_type _is_convertable_to_member_function_ptr_tester(dummy_class_f); _no_type _is_convertable_to_member_function_ptr_tester(...); _yes_type _is_convertable_to_const_member_function_ptr_tester(dummy_class_f_const); _no_type _is_convertable_to_const_member_function_ptr_tester(...); template<class _Tp> _yes_type _is_convertable_to_ptr_tester(_Tp*); template<class> _no_type _is_convertable_to_ptr_tester(...); }
在这里,我们将使用与第二章相同的原理,即countof及其通过模板函数COUNTOF_REQUIRES_ARRAY_ARGUMENT 的返回值(元素数组)的sizeof进行定义。
template<class T> struct _is_convertable_to_void_ptr_impl { static const bool value = (sizeof(nullptr_detail::_is_convertable_to_void_ptr_tester((T) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)); };
这是怎么回事 首先,编译器“
迭代 ”
_is_convertable_to_void_ptr_tester函数的重载,该
重载具有类型
T的参数和
NULL值 (该值不起作用,只是
NULL必须为Type
T )。 只有两个重载
-void *类型和
变量参数列表(...) 。 将参数替换为这些重载中的每一个,如果类型强制转换为指向
void的指针,则编译器将选择第一个,如果无法执行
强制转换 ,则选择第二个。 使用编译器选择的重载后,我们使用
sizeof来确定函数返回的值的大小,并且由于保证了它们的不同(
sizeof( _no_type ) == 8 ,
sizeof( _yes_type ) == 1 ),我们可以确定编译器选择并转换的重载大小。我们的类型是否为
空* 。
我们将进一步应用相同的编程模板,以确定是否将我们选择的表示
nullptr_t的类型的对象
转换为任何指针(本质上
(T)( STDEX_NULL )是
nullptr的未来定义)。
template<class T> struct _is_convertable_to_member_function_ptr_impl { static const bool value = (sizeof(nullptr_detail::_is_convertable_to_member_function_ptr_tester((T) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)) && (sizeof(nullptr_detail::_is_convertable_to_const_member_function_ptr_tester((T) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)); }; template<class NullPtrType, class T> struct _is_convertable_to_any_ptr_impl_helper { static const bool value = (sizeof(nullptr_detail::_is_convertable_to_ptr_tester<T>((NullPtrType) (STDEX_NULL))) == sizeof(nullptr_detail::_yes_type)); }; template<class T> struct _is_convertable_to_any_ptr_impl { static const bool value = _is_convertable_to_any_ptr_impl_helper<T, int>::value && _is_convertable_to_any_ptr_impl_helper<T, float>::value && _is_convertable_to_any_ptr_impl_helper<T, bool>::value && _is_convertable_to_any_ptr_impl_helper<T, const bool>::value && _is_convertable_to_any_ptr_impl_helper<T, volatile float>::value && _is_convertable_to_any_ptr_impl_helper<T, volatile const double>::value && _is_convertable_to_any_ptr_impl_helper<T, nullptr_detail::dummy_class>::value; }; template<class T> struct _is_convertable_to_ptr_impl { static const bool value = ( _is_convertable_to_void_ptr_impl<T>::value == bool(true) && _is_convertable_to_any_ptr_impl<T>::value == bool(true) && _is_convertable_to_member_function_ptr_impl<T>::value == bool(true) ); };
当然,不可能使用
volatile和
const 修饰符遍历所有可能的和难以想象的指针及其组合,因此,我只限于这9个检查(两个指向类函数的指针,一个指向
void的指针,七个指向不同类型的指针),就足够了。
如上所述,某些(* khe-khe * ... Borland Builder 6.0 ... * khe *)编译器无法区分类型的指针和类的成员,因此我们将针对这种情况编写另一种辅助检查,以便我们可以通过该类选择所需的
nullptr_t实现如果需要的话。
struct _member_ptr_is_same_as_ptr { struct test {}; typedef void(test::*member_ptr_type)(void); static const bool value = _is_convertable_to_void_ptr_impl<member_ptr_type>::value; }; template<bool> struct _nullptr_t_as_class_chooser { typedef nullptr_detail::nullptr_t_as_class_impl type; }; template<> struct _nullptr_t_as_class_chooser<false> { typedef nullptr_detail::nullptr_t_as_class_impl1 type; };
然后剩下的只是检查
nullptr_t的不同实现,并为编译器选择适当的编译器。
选择nullptr_t实现 template<bool> struct _nullptr_choose_as_int { typedef nullptr_detail::nullptr_t_as_int type; }; template<bool> struct _nullptr_choose_as_enum { typedef nullptr_detail::nullptr_t_as_enum type; }; template<bool> struct _nullptr_choose_as_class { typedef _nullptr_t_as_class_chooser<_member_ptr_is_same_as_ptr::value>::type type; }; template<> struct _nullptr_choose_as_int<false> { typedef nullptr_detail::nullptr_t_as_void type; }; template<> struct _nullptr_choose_as_enum<false> { struct as_int { typedef nullptr_detail::nullptr_t_as_int nullptr_t_as_int; static const bool _is_convertable_to_ptr = _is_convertable_to_ptr_impl<nullptr_t_as_int>::value; static const bool _equal_void_ptr = _is_equal_size_to_void_ptr<nullptr_t_as_int>::value; }; typedef _nullptr_choose_as_int<as_int::_is_convertable_to_ptr == bool(true) && as_int::_equal_void_ptr == bool(true)>::type type; }; template<> struct _nullptr_choose_as_class<false> { struct as_enum { typedef nullptr_detail::nullptr_t_as_enum nullptr_t_as_enum; static const bool _is_convertable_to_ptr = _is_convertable_to_ptr_impl<nullptr_t_as_enum>::value; static const bool _equal_void_ptr = _is_equal_size_to_void_ptr<nullptr_t_as_enum>::value; static const bool _can_be_ct_constant = true;
首先,我们检查是否可以将
nullptr_t表示为类,但是由于我没有找到
独立解决方案的通用编译器,因此我没有找到可以作为编译时间常数的类型对象(顺便说一句,我很乐意接受有关此问题的建议,因为这很可能实现),始终
选中此选项(
_can_be_ct_constant始终为
false )。 接下来,我们切换到通过
enum查看带有视图的变量。 如果仍然无法显示(编译器无法通过
枚举显示指针或大小不正确),那么我们尝试将其表示为整数类型(其大小将等于
void指针的大小)。 好吧,即使这不起作用,也可以通过
void *选择
nullptr_t类型的实现。
至此,揭示了SFINAE与C ++模板结合使用的大多数功能,因此可以选择必要的实现,而不必依赖于编译器相关的宏,甚至不依赖于宏(与boost不同,在所有这些情况下,
#ifdef #else#检查都会
塞满这些东西)
endif )。
剩下的只是为
命名空间 stdex中的
nullptr_t定义一个类型别名,以及为
nullptr定义(为了遵守不能采用
nullptr地址的另一个标准要求,以及使用
nullptr作为编译时间常数)。
namespace stdex { typedef detail::_nullptr_chooser::type nullptr_t; } #define nullptr (stdex::nullptr_t)(STDEX_NULL)
第三章结束。 在
第四章中,我终于了解了type_traits以及在开发过程中遇到的编译器中的其他错误。
谢谢您的关注。