
先前部分的摘要
由于使用C ++ 11编译器的能力受到限制,并且由于缺乏替代性,boost希望在编译器随附的C ++ 98 / C ++ 03库之上编写自己的标准C ++ 11库实现。
除了标准头文件
type_traits之外 ,还添加
了 thread ,
Mutex ,
chrono ,
nullptr.h ,
它们实现了
std :: nullptr_t和
core.h ,其中添加了与
依赖于编译器的
功能相关的宏,并扩展了标准库。
链接到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模板库还需要什么第五章
...
第2章。#ifndef __CPP11_SUPPORT__#定义__COMPILER_SPECIFIC_BUILT_IN_AND_MACRO_HELL__ #endif
在对所有代码进行了一些梳理并将其按“标准”标头划分为单独的
命名空间stdex后,我继续填充
type_traits ,
nullptr.h以及相同的
core.h ,其中包含宏来确定编译器使用并支持的标准版本
本机nullptr ,
char16_t ,
char32_t和
static_assert 。
从理论上讲,一切都很简单-根据C ++标准
(第14.8条), __cplusplus宏必须由编译器定义,并与受支持标准的版本相对应:
C++ pre-C++98: #define __cplusplus 1 C++98: #define __cplusplus 199711L C++98 + TR1: #define __cplusplus 199711L
因此,确定支持可用性的代码很简单:
#if (__cplusplus >= 201103L)

实际上,并非所有事情都那么简单,现在有趣的拐杖开始了。
首先,不是所有的编译器,甚至没有编译器都没有完全,立即实现下一个标准。 例如,在Visual Studio 2013
中,很长一段时间
都没有
constexpr ,而它声称它支持C ++ 11-并警告说实现不完整。 也就是说,
auto -please,
static_assert-一样容易(即使从早期的MS VS起也一样),但是
constexpr 却不容易。 其次,并不是所有的编译器(而且更令人惊讶)都正确地公开了此定义并及时进行了更新。 突然,在同一个编译器中,Visual Studio
并未从编译器的第一个版本中
更改__cplusplus 定义的版本,尽管早就宣布了对C ++ 11的完全支持(这也不是真的,对此存在不同的不满之情-一旦对话涉及“新功能”的特定功能时) “ 11位标准开发人员立即说,没有C99预处理器,没有其他”功能”)。 如果标准编译器不完全符合声明的标准,则允许编译器将此定义设置为与上述值不同的情况,这使情况更加恶化。 例如,假设为给定的宏进行这样的定义定义是合理的(随着新功能的引入,增加了隐藏在该定义后面的数量):
standart C++98: #define __cplusplus 199711L
但是与此同时,没有一个主要的流行编译器对此功能“磨损”。
由于所有这些(我不怕这个词),现在,对于每个非标准编译器,您都必须编写自己的特定检查,以找出哪种C ++标准及其支持的程度。 好消息是,我们只需要学习一些编译器功能即可正常工作。 首先,我们现在通过
_MSC_VER宏添加Visual Studio的版本检查,此编译器是唯一的。 因为在我所支持的编译器库中,还有C ++ Borland Builder 6.0,而其开发人员又非常热衷于与Visual Studio兼容(包括其“功能”和错误),所以突然之间也有了这个宏。 对于兼容clang的编译器,有一个非标准宏
__has_feature( feature_name
) ,您可以通过它查看编译器是否支持该功能。 结果,代码膨胀为:
#ifndef __has_feature #define __has_feature(x) 0
是否想接触更多的编译器? 我们添加了对Codegear C ++ Builder的检查,该检查器是Borland的继承人(处于最坏的表现,但稍后会详细介绍):
#ifndef __has_feature #define __has_feature(x) 0
还值得注意的是,由于Visual Studio已经支持
_MSC_VER 1600编译器版本的
nullptr以及内置类型
char16_t和
char32_t ,因此我们需要正确处理此问题。 添加了更多检查:
#ifndef __has_feature #define __has_feature(x) 0
同时,我们还将检查是否支持C ++ 98,因为对于不带C ++ 98的编译器,标准库中不会包含某些头文件,并且我们无法使用编译器来验证它们的不存在。
全选 #ifndef __has_feature #define __has_feature(x) 0
现在,来自boost的大量配置开始出现在我的记忆中,许多努力工作的开发人员编写了所有这些依赖于编译器的宏,并绘制了特定版本的特定编译器所支持和不支持的内容的地图,我个人对此感到不安,我永远不要看或触摸它。 但好消息是您可以在这里停下来。 至少对于我来说,这足以支持大多数流行的编译器,但是如果您发现不准确或想要添加其他编译器,我将非常乐意接受请求请求。
与boost相比,这是一个了不起的成就,我相信可以在代码中保持与编译器相关的宏的分布,这使代码更整洁,更易于理解,并且不会为每个OS和每个编译器堆积数十个配置文件。 稍后我们将讨论这种方法的缺点。
在这一阶段,我们已经可以开始连接11种标准中缺少的功能,而我们首先介绍的是
static_assert 。
static_assert
我们定义了
StaticAssertion结构,该结构将布尔值作为模板参数-将存在我们的条件,如果不满足(表达式为
false ),则在编译非专用模板时会发生错误。 另一个用于接收
sizeof( StaticAssertion )的虚拟结构。
namespace stdex { namespace detail { template <bool> struct StaticAssertion; template <> struct StaticAssertion<true> { };
以及进一步的宏观魔术
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define STATIC_ASSERT(expression, message) static_assert((expression), #message) #else
用法:
STATIC_ASSERT(sizeof(void*) == 4, non_x32_platform_is_unsupported);
我的实现与标准实现之间的重要区别是,在不告知用户的情况下,不会重载此关键字。 这是由于以下事实:在C ++中,不可能定义多个具有不同数量的参数但一个名称的定义,并且没有消息的实现比所选选项的用处少得多。 此功能导致以下事实:从本质上讲 ,我的实现中的STATIC_ASSERT是已经在C ++ 11中添加的版本。
让我们看看发生了什么。 通过检查
__cplusplus和非标准的编译器宏的版本,我们获得了有关_STDEX_NATIVE_CPP11_SUPPORT
define表示的有关C ++ 11支持(以及因此的
static_assert )的足够信息。 因此,如果定义了此宏,我们可以简单地使用标准的
static_assert :
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define STATIC_ASSERT(expression, message) static_assert((expression), #message)
请注意, STATIC_ASSERT宏的第二个参数根本不是字符串文字,因此使用预处理程序运算符#我们将把message参数转换为字符串,以传输到标准static_assert 。
如果我们没有编译器的支持,则继续执行。 首先,我们将声明用于“粘合”字符串的辅助宏(预处理程序运算符
##对此负责)。
#define CONCATENATE(arg1, arg2) CONCATENATE1(arg1, arg2) #define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2) #define CONCATENATE2(arg1, arg2) arg1##arg2
为了能够将同一CONCATENATE宏的结果作为参数传递给arg1和arg2 ,我特别不是简单地使用#define CONCATENATE( arg1 , arg2 ) arg1 ## arg2 。
接下来,我们以漂亮的名称__static_assertion_at_line_ {行号}声明一个结构(宏
__LINE__也由标准定义,并且必须扩展为调用它的行号),然后在该结构内添加一个类型为
StaticAssertion的字段,名称为STATIC_ASSERTION_FAILED_AT_LINE_ {行号} _WITH来自调用宏的错误消息}。
#define STATIC_ASSERT(expression, message)\ struct CONCATENATE(__static_assertion_at_line_, __LINE__)\ {\ stdex::detail::StaticAssertion<static_cast<bool>((expression))> CONCATENATE(CONCATENATE(CONCATENATE(STATIC_ASSERTION_FAILED_AT_LINE_, __LINE__), _WITH__), message);\ };\ typedef stdex::detail::StaticAssertionTest<sizeof(CONCATENATE(__static_assertion_at_line_, __LINE__))> CONCATENATE(__static_assertion_test_at_line_, __LINE__)
使用StaticAssertion中的 template参数
,我们传递一个在
STATIC_ASSERT中检查的表达式,将其转换为
bool 。 最后,为了避免创建局部变量和零开销检查用户条件,声明了类型为
StaticAssertionTest <sizeof({上面声明的结构的名称
})的别名,名称为__static_assertion_test_at_line_ {行号}。
命名的所有美感仅是为了从编译错误中清楚地表明这是一个肯定的结果,而不仅是一个错误,而且还显示为此断言设置的错误消息。
sizeof技巧是强制编译器实例化
StaticAssertion模板类的必要条件,该模板类位于刚刚声明的结构内部,从而检查传递给assert的条件。
STATIC_ASSERT结果GCC:
30:103:错误:字段'STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported'具有不完整的类型'stdex :: detail :: StaticAssertion <false>'
25:36:注意:在宏“ CONCATENATE2”的定义中
23:36:注意:扩展宏“ CONCATENATE1”
30:67:注意:扩展宏“ CONCATENATE”
24:36:注意:扩展宏“ CONCATENATE2”
23:36:注意:扩展宏“ CONCATENATE1”
30:79:注意:扩展宏“ CONCATENATE”
24:36:注意:扩展宏“ CONCATENATE2”
23:36:注意:扩展宏“ CONCATENATE1”
30:91:注意:在宏“ CONCATENATE”的扩展中
36:3:注意:在宏“ STATIC_ASSERT”的扩展中
Borland C ++ Builder:
[C ++错误] stdex_test.cpp(36):E2450未定义结构'stdex :: detail :: StaticAssertion <0>'
[C ++错误] stdex_test.cpp(36):E2449“ STATIC_ASSERTION_FAILED_AT_LINE_36_WITH__non_x32_platform_is_unsupported”的大小未知或为零
[C ++错误] stdex_test.cpp(36):E2450未定义结构'stdex :: detail :: StaticAssertion <0>'
Visual Studio:
错误c2079
我想拥有的第二个“技巧”是
countof-计算数组中元素的数量。 Sishers非常喜欢通过sizeof(arr)/ sizeof(arr [0])声明此宏,但是我们将继续。
数
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #include <cstddef> namespace stdex { namespace detail { template <class T, std::size_t N> constexpr std::size_t _my_countof(T const (&)[N]) noexcept { return N; } } // namespace detail } #define countof(arr) stdex::detail::_my_countof(arr) #else //no C++11 support #ifdef _STDEX_NATIVE_MICROSOFT_COMPILER_EXTENSIONS_SUPPORT // Visual C++ fallback #include <stdlib.h> #define countof(arr) _countof(arr) #elif defined(_STDEX_NATIVE_CPP_98_SUPPORT)// C++ 98 trick #include <cstddef> template <typename T, std::size_t N> char(&COUNTOF_REQUIRES_ARRAY_ARGUMENT(T(&)[N]))[N]; #define countof(x) sizeof(COUNTOF_REQUIRES_ARRAY_ARGUMENT(x)) #else #define countof(arr) sizeof(arr) / sizeof(arr[0]) #endif
对于支持
constexpr的编译器,
我们将声明此模板的constexpr版本(对于所有标准而言,绝对不需要通过
COUNTOF_REQUIRES_ARRAY_ARGUMENT模板实现),其余的则通过模板函数
COUNTOF_REQUIRES_ARRAY_ARGUMENT引入 。 Visual Studio在这里再次以
stdlib.h头文件中存在
_countof自己的实现而
著称 。
COUNTOF_REQUIRES_ARRAY_ARGUMENT函数看起来很吓人,
弄清楚它的作用非常棘手。 如果仔细观察,您会理解它采用模板类型
T和大小
N的元素数组作为唯一参数-因此,在传输其他类型的元素(不是数组)的情况下,我们会遇到编译错误,这无疑是令人高兴的。 仔细观察一下,您可以发现(很难)返回了大小为
N的
char元素数组
。 问题是,为什么我们需要所有这些? 这是
sizeof运算符
发挥作用的地方 ,它在编译时具有独特的工作能力。 调用
sizeof( COUNTOF_REQUIRES_ARRAY_ARGUMENT )确定函数返回的
char元素数组的大小,并且由于标准
sizeof(char) == 1,因此这是原始数组中
N个元素的数量。 优雅,美丽,完全免费。
永远
我
永远需要另一个无限循环的小辅助宏,这是
永远的 。 定义如下:
#if !defined(forever) #define forever for(;;) #else #define STRINGIZE_HELPER(x) #x #define STRINGIZE(x) STRINGIZE_HELPER(x) #define WARNING(desc) message(__FILE__ "(" STRINGIZE(__LINE__) ") : warning: " desc) #pragma WARNING("stdex library - macro 'forever' was previously defined by user; ignoring stdex macro definition") #undef STRINGIZE_HELPER #undef STRINGIZE #undef WARNING #endif
定义显式无限循环的示例语法:
unsigned int i = 0; forever { ++i; }
该宏仅用于显式定义无限循环,并且仅出于“添加语法糖”的原因而包含在库中。 将来,我建议可以通过定义
FOREVER插件宏来替换它。 在上述库代码片段中,最引人注目的是如果用户已经定义了
forever宏,则同一
WARNING宏会在所有编译器中生成警告消息。 它使用熟悉的标准
__LINE__宏和标准
__FILE__宏 ,该
宏将转换为具有当前源文件名称的字符串。
stdex_assert
为了在运行时实现
断言 ,将宏
stdex_assert引入为:
#if defined(assert) #ifndef NDEBUG #include <iostream> #define stdex_assert(condition, message) \ do { \ if (! (condition)) { \ std::cerr << "Assertion `" #condition "` failed in " << __FILE__ \ << " line " << __LINE__ << ": " << message << std::endl; \ std::terminate(); \ } \ } while (false) #else #define stdex_assert(condition, message) ((void)0) #endif #endif
我不会说我为这个实现感到非常自豪(将来会更改),但是在这里使用了一种有趣的技术,我想引起大家的注意。 为了从应用程序代码的范围中隐藏检查,使用了
do {} while(false)构造,该构造将被执行,这很明显,并且一次不会在通用应用程序代码中引入“服务”代码。 该技术非常有用,并在库中的其他多个地方使用。
否则,实现与标准
断言非常相似-使用某个
NDEBUG宏(编译器通常在发行版本中设置该宏),断言什么都不做,否则如果不满足断言条件,则会中断程序的执行并将消息输出到标准错误流。
没有
对于不引发异常的函数,新标准中引入了
noexcept关键字。 通过宏实现也是非常简单和轻松的:
#ifdef _STDEX_NATIVE_CPP11_SUPPORT #define stdex_noexcept noexcept #else #define stdex_noexcept throw() #endif
但是,有必要了解,按标准,
noexcept可以采用值
bool ,并且还
可以用于确定在编译时传递给它的表达式不会引发异常。 如果没有编译器支持,则无法实现此功能,因此该库中只有“精简”的
stdex_noexcept 。
第二章结束。
第三章将讨论nullptr实现的复杂性,为什么不同的编译器会有所不同,type_traits的开发以及在开发过程中遇到的编译器中的其他错误。
谢谢您的关注。