我们已经零零碎碎提到过几次 Boost 了。作为 C 世界里标准库之外最知名的开放源码程序库我们值得专门用一讲来讨论一下 Boost。Boost 概览Boost 的网站把 Boost 描述成为经过同行评审的、可移植的 C 源码库peer-reviewed portable C source libraries。换句话说它跟很多个人开源库不一样的地方在于它的代码是经过评审的。事实上Boost 项目的背后有很多 C 专家比如发起人之一的 Dave Abarahams 是 C 标准委员会的成员也是《C 模板元编程》一书的作者。这也就使得 Boost 有了很不一样的特殊地位它既是 C 标准库的灵感来源之一也是 C 标准库的试验田。下面这些 C 标准库就源自 Boost当然将来还会有新的库从 Boost 进入 C 标准如网络库的标准化就是基于 Boost.Asio 进行的。因此即使相关的功能没有被标准化我们也可能可以从 Boost 里看到某个功能可能会被标准化的样子——当然最终标准化之后的样子还是经常有所变化的。我们也可以在我们的编译器落后于标准、不能提供标准库的某个功能时使用 Boost 里的替代品。比如我之前提到过老版本的 macOS 上苹果的编译器不支持 optional 和 variant。除了我描述的不正规做法改用 Boost 也是方法之一。比如对于 variant所需的改动只是这样就基本大功告成了。作为一个准标准的库很多环境里缺省会提供 Boost。这种情况下在程序里使用 Boost 不会额外增加编译或运行时的依赖减少了可能的麻烦。如果我需要某个功能在标准库里没有在 Boost 里有我会很乐意直接使用 Boost 里的方案而非另外去查找。如果我要使用非 Boost 的第三方库的话那一般要么是 Boost 里没有要么就是那个库比 Boost 里的要好用很多了。鉴于 Boost 是一个库集合当前版本1.72有 160 个独立库即使写本书也不可能完整地讨论所有的库。这一讲里我们也就管中窥豹式地浏览几个 Boost 库。具体你需要什么还是得你自己回头去细细品味。Boost 的安装在主要的开发平台上现在你都可以直接安装 Boost而不需要自己从源代码编译了如果你在某个平台上使用非缺省的编译器如在 Windows 上或 macOS 上使用 GCC一般就需要自己编译了具体步骤请参见 Boost 的文档。不过很多 Boost 库是完全不需要编译的只需要把头文件加到编译器能找到的路径里就可以——如我们上一讲讨论的 Boost.Multiprecision 就是这样。我们讨论 Boost 库的时候也会提一下使用这个库是否需要链接某个 Boost 库——需要的话也就意味着需要编译和安装这个 Boost 库。Boost.TypeIndexTypeIndex 是一个很轻量级的库它不需要链接解决的也是使用模板时的一个常见问题如何精确地知道一个表达式或变量的类型。我们还是看一个例子#include iostream #include typeinfo #include utility #include vector #include boost/type_index.hpp using namespace std; using boost::typeindex::type_id; using boost::typeindex:: type_id_with_cvr; int main() { vectorint v; auto it v.cbegin(); cout *** Using typeid\n; cout typeid(const int).name() endl; cout typeid(v).name() endl; cout typeid(it).name() endl; cout *** Using type_id\n; cout type_idconst int() endl; cout type_iddecltype(v)() endl; cout type_iddecltype(it)() endl; cout *** Using type_id_with_cvr\n; cout type_id_with_cvrconst int() endl; cout type_id_with_cvrdecltype( (v))() endl; cout type_id_with_cvrdecltype( move((v)))() endl; cout type_id_with_cvrdecltype( (it))() endl; }上面的代码里展示了标准的 typeid 和 Boost 的 type_id 和 type_id_with_cvr 的使用。它们的区别是typeid 是标准 C 的关键字可以应用到变量或类型上返回一个 std::type_info。我们可以用它的 name 成员函数把结果转换成一个字符串但标准不保证这个字符串的可读性和唯一性。type_id 是 Boost 提供的函数模板必须提供类型作为模板参数——所以对于表达式和变量我们需要使用 decltype。结果可以直接输出到 IO 流上。type_id_with_cvr 和 type_id 相似但它获得的结果会包含 const/volatile 状态及引用类型。上面程序在 MSVC 下的输出为在 GCC 下的输出为我们可以看到 MSVC 下 typeid 直接输出了比较友好的类型名称但 GCC 下没有。此外我们可以注意到typeid 的输出忽略了 const 修饰也不能输出变量的引用类型。type_id 保证可以输出友好的类型名称输出时也不需要调用成员函数但例子里它忽略了 int 的 const 修饰也和 typeid 一样不能输出表达式的引用类型。type_id_with_cvr 可以输出 const/volatile 状态和引用类型注意这种情况下模板参数必须包含引用类型所以我用了 decltype((v)) 这种写法而不是 decltype(v)。如果你忘了这两者的区别请复习一下的 decltype。显然除非你正在使用 MSVC否则调试期 typeid 的用法完全应该用 Boost 的 type_id 来替代。另外如果你的开发环境要求禁用 RTTI运行时类型识别那 typeid 在 Clang 和 GCC 下根本不能使用而使用 Boost.TypeIndex 库仍然没有问题。当然上面说的前提都是你在调试中试图获得变量的类型而不是要获得一个多态对象的运行时类型。后者还是离不开 RTTI 的——虽然你也可以用一些其他方式来模拟 RTTI但我个人觉得一般的项目不太有必要这样做。下面的代码展示了 typeid 和 type_id 在获取对象类型上的差异#include iostream #include typeinfo #include boost/type_index.hpp using namespace std; using boost::typeindex::type_id; class shape { public: virtual ~shape() {} }; class circle : public shape {}; #define CHECK_TYPEID(object, type) \ cout typeid( #object ) \ (typeid(object) \ typeid(type) \ ? is \ : is NOT ) \ #type endl #define CHECK_TYPE_ID(object, \ type) \ cout type_id( #object \ ) \ (type_iddecltype( \ object)() \ type_idtype() \ ? is \ : is NOT ) \ #type endl int main() { shape* ptr new circle(); CHECK_TYPEID(*ptr, shape); CHECK_TYPEID(*ptr, circle); CHECK_TYPE_ID(*ptr, shape); CHECK_TYPE_ID(*ptr, circle); delete ptr; }Boost.CoreCore 里面提供了一些通用的工具这些工具常常被 Boost 的其他库用到而我们也可以使用不需要链接任何库。在这些工具里有些已经可能经过一些变化后进入了 C 标准如addressof在即使用户定义了 operator 时也能获得对象的地址enable_if这个我们已经深入讨论过了is_same判断两个类型是否相同C11 开始在 中定义ref和标准库的相同我们在讨论线程时用过我们在剩下的里面来挑几个讲讲。boost::core::demangleboost::core::demangle 能够用来把 typeid 返回的内部名称“反粉碎”demangle成可读的形式看代码和输出应该就非常清楚了#include iostream #include typeinfo #include vector #include boost/core/demangle.hpp using namespace std; using boost::core::demangle; int main() { vectorint v; auto it v.cbegin(); cout *** Using typeid\n; cout typeid(const int).name() endl; cout typeid(v).name() endl; cout typeid(it).name() endl; cout *** Demangled\n; cout demangle(typeid(const int) .name()) endl; cout demangle(typeid(v).name()) endl; cout demangle( typeid(it).name()) endl; }GCC 下的输出为如果你不使用 RTTI 的话那直接使用 TypeIndex 应该就可以。如果你需要使用 RTTI、又不是只使用 MSVC 的话demangle 就会给你不少帮助。boost::noncopyableboost::noncopyable 提供了一种非常简单也很直白的把类声明成不可拷贝的方式。比如我们里的 shape_wrapper用下面的写法就明确表示了它不允许被拷贝#include boost/core/noncopyable.hpp class shape_wrapper : private boost::noncopyable { … };你当然也可以自己把拷贝构造和拷贝赋值函数声明成 delete不过上面的写法是不是可读性更佳boost::swap你有没有印象在通用的代码如何对一个不知道类型的对象执行交换操作不记得的话标准做法是这样的{ using std::swap; swap(lhs, rhs); }即我们需要在某个小作用域里引入 std::swap然后让编译器在“看得到” std::swap 的情况下去编译 swap 指令。根据 ADL如果在被交换的对象所属类型的名空间下有 swap 函数那个函数会被优先使用否则编译器会选择通用的 std::swap。似乎有点小啰嗦。使用 Boost 的话你可以一行搞定boost::swap(lhs, rhs);当然你需要包含头文件 。Boost.ConversionConversion 同样是一个不需要链接的轻量级的库。它解决了标准 C 里的另一个问题标准类型之间的转换不够方便。在 C11 之前这个问题尤为严重。在 C11 里标准引入了一系列的函数已经可以满足常用类型之间的转换。但使用 Boost.Conversion 里的 lexical_cast 更不需要去查阅方法名称或动脑子去努力记忆。下面是一个例子#include iostream #include stdexcept #include string #include boost/lexical_cast.hpp using namespace std; using boost::bad_lexical_cast; using boost::lexical_cast; int main() { // 整数到字符串的转换 int d 42; auto d_str lexical_caststring(d); cout d_str endl; // 字符串到浮点数的转换 auto f lexical_castfloat(d_str) / 4.0; cout f endl; // 测试 lexical_cast 的转换异常 try { int t lexical_castint(x); cout t endl; } catch (bad_lexical_cast e) { cout e.what() endl; } // 测试标准库 stoi 的转换异常 try { int t std::stoi(x); cout t endl; } catch (invalid_argument e) { cout e.what() endl; } }GCC 下的输出为我觉得 GCC 里 stoi 的异常输出有点太言简意赅了……而 lexical_cast 的异常输出在不同的平台上有很好的一致性。Boost.ScopeExit我们说过 RAII 是推荐的 C 里管理资源的方式。不过作为 C 程序员跟 C 函数打交道也很正常。每次都写个新的 RAII 封装也有点浪费。Boost 里提供了一个简单的封装你可以从下面的示例代码里看到它是如何使用的#include stdio.h #include boost/scope_exit.hpp void test() { FILE* fp fopen(test.cpp, r); if (fp NULL) { perror(Cannot open file); } BOOST_SCOPE_EXIT(fp) { if (fp) { fclose(fp); puts(File is closed); } } BOOST_SCOPE_EXIT_END puts(Faking an exception); throw 42; } int main() { try { test(); } catch (int) { puts(Exception received); } }唯一需要说明的可能就是 BOOST_SCOPE_EXIT 里的那个 符号了——把它理解成 lambda 表达式的按引用捕获就对了虽然 BOOST_SCOPE_EXIT 可以支持 C98 的代码。如果不需要捕获任何变量BOOST_SCOPE_EXIT 的参数必须填为 void。输出为假设 test.cpp 存在使用这个库也只需要头文件。注意实现类似的功能在 C11 里相当容易但由于 ScopeExit 可以支持 C98 的代码因而它的实现还是相当复杂的。Boost.Program_options传统上 C 代码里处理命令行参数会使用 getopt。我也用过比如在下面的代码中breaktext/breaktext.c at master · adah1972/breaktext · GitHub这种方式有不少缺陷一个选项通常要在三个地方重复说明文本里getopt 的参数里以及对 getopt 的返回结果进行处理时。不知道你觉得怎样我反正发生过改了一处、漏改其他的错误。对选项的附加参数需要手工写代码处理因而常常不够严格C 的类型转换不够方便尤其是检查错误。Program_options 正是解决这个问题的。这个代码有点老了不过还挺实用懒得去找特别的处理库时至少这个伸手可用。使用这个库需要链接 boost_program_options 库。下面的代码展示了代替上面的 getopt 用法的代码#include iostream #include string #include stdlib.h #include boost/program_options.hpp namespace po boost::program_options; using std::cout; using std::endl; using std::string; string locale; string lang; int width 72; bool keep_indent false; bool verbose false; int main(int argc, char* argv[]) { po::options_description desc( Usage: breaktext [OPTION]... Input File [Output File]\n \n Available options); desc.add_options() (locale,L, po::valuestring(locale), Locale of the console (system locale by default)) (lang,l, po::valuestring(lang), Language of input (asssume no language by default)) (width,w, po::valueint(width), Width of output text (72 by default)) (help,h, Show this help message and exit) (,i, po::bool_switch(keep_indent), Keep space indentation) (,v, po::bool_switch(verbose), Be verbose); po::variables_map vm; try { po::store( po::parse_command_line( argc, argv, desc), vm); } catch (po::error e) { cout e.what() endl; exit(1); } vm.notify(); if (vm.count(help)) { cout desc \n; exit(1); } }这样我们的程序就能处理上面的那些选项了。如果运行时在命令行加上 -h 或 --help 选项程序就会输出跟原来类似的帮助输出——额外的好处是选项的描述信息较长时还能自动帮你折行不需要手工排版了。建议你自己尝试一下提供各种正确或错误的选项来检查一下运行的结果。当然现在有些更新的选项处理库但它们应该都和 Program_options 更接近而不是和 getopt 更接近。如果你感觉 Program_options 功能不足了换一个其他库不会是件麻烦事。Boost.HanaBoost 里自然也有模板元编程相关的东西。但我不打算介绍 MPL、Fusion 和 Phoenix 那些因为有些技巧在 C11 和 Lambda 表达式到来之后已经略显得有点过时了。Hana 则不同它是一个使用了 C11/14 实现技巧和惯用法的新库也和一般的模板库一样只要有头文件就能使用。Hana 里定义了一整套供编译期使用的数据类型和函数。我们现在看一下它提供的部分类型type把类型转化成对象我们在曾经示例过相反的动作把数值转化成对象来方便后续处理。integral_constant跟 std::integral_constant 相似但定义了更多的运算符和语法糖。特别的你可以用字面量来生成一个 long long 类型的 integral_constant如 1_c。string一个编译期使用的字符串类型。tuple跟 std::tuple 类似意图是当作编译期的 vector 来使用。map编译期使用的关联数组。set编译期使用的集合。Hana 里的算法的名称跟标准库的类似我就不一一列举了。下面的例子展示了一个基本用法#include boost/hana.hpp namespace hana boost::hana; class shape {}; class circle {}; class triangle {}; int main() { using namespace hana::literals; constexpr auto tup hana::make_tuple( hana::type_cshape*, hana::type_ccircle, hana::type_ctriangle); constexpr auto no_pointers hana::remove_if( tup, [](auto a) { return hana::traits:: is_pointer(a); }); static_assert( no_pointers hana::make_tuple( hana::type_ccircle, hana::type_ctriangle)); static_assert( hana::reverse(no_pointers) hana::make_tuple( hana::type_ctriangle, hana::type_ccircle)); static_assert( tup[1_c] hana::type_ccircle); }这个程序可以编译但没有任何运行输出。在这个程序里我们做了下面这几件事使用 type_c 把类型转化成 type 对象并构造了类型对象的 tuple使用 remove_if 算法移除了 tup 中的指针类型使用静态断言确认了结果是我们想要的使用静态断言确认了可以用 reverse 把 tup 反转一下使用静态断言确认了可以用方括号运算符来获取 tup 中的某一项可以看到Hana 本质上以类似普通的运行期编程的写法来做编译期的计算。上面展示的只是一些最基本的用法而 Hana 的文档里展示了很多有趣的用法。尤其值得一看的是文档中展示了如何利用 Hana 提供的机制来自己定义 switch_、case_、default_使得下面的代码可以通过编译boost::any a x; std::string r switch_(a)( case_int([](auto i) { return int: s std::to_string(i); }), case_char([](auto c) { return char: s std::string{c}; }), default_( [] { return unknowns; })); assert(r char: xs);我个人认为很有意思。内容小结本讲我们对 Boost 的意义做了概要介绍并蜻蜓点水地简单描述了若干 Boost 库的功能。如果你想进一步了解 Boost 的细节的话就得自行查看文档了。
现代C++:Boost:你需要的“瑞士军刀”
我们已经零零碎碎提到过几次 Boost 了。作为 C 世界里标准库之外最知名的开放源码程序库我们值得专门用一讲来讨论一下 Boost。Boost 概览Boost 的网站把 Boost 描述成为经过同行评审的、可移植的 C 源码库peer-reviewed portable C source libraries。换句话说它跟很多个人开源库不一样的地方在于它的代码是经过评审的。事实上Boost 项目的背后有很多 C 专家比如发起人之一的 Dave Abarahams 是 C 标准委员会的成员也是《C 模板元编程》一书的作者。这也就使得 Boost 有了很不一样的特殊地位它既是 C 标准库的灵感来源之一也是 C 标准库的试验田。下面这些 C 标准库就源自 Boost当然将来还会有新的库从 Boost 进入 C 标准如网络库的标准化就是基于 Boost.Asio 进行的。因此即使相关的功能没有被标准化我们也可能可以从 Boost 里看到某个功能可能会被标准化的样子——当然最终标准化之后的样子还是经常有所变化的。我们也可以在我们的编译器落后于标准、不能提供标准库的某个功能时使用 Boost 里的替代品。比如我之前提到过老版本的 macOS 上苹果的编译器不支持 optional 和 variant。除了我描述的不正规做法改用 Boost 也是方法之一。比如对于 variant所需的改动只是这样就基本大功告成了。作为一个准标准的库很多环境里缺省会提供 Boost。这种情况下在程序里使用 Boost 不会额外增加编译或运行时的依赖减少了可能的麻烦。如果我需要某个功能在标准库里没有在 Boost 里有我会很乐意直接使用 Boost 里的方案而非另外去查找。如果我要使用非 Boost 的第三方库的话那一般要么是 Boost 里没有要么就是那个库比 Boost 里的要好用很多了。鉴于 Boost 是一个库集合当前版本1.72有 160 个独立库即使写本书也不可能完整地讨论所有的库。这一讲里我们也就管中窥豹式地浏览几个 Boost 库。具体你需要什么还是得你自己回头去细细品味。Boost 的安装在主要的开发平台上现在你都可以直接安装 Boost而不需要自己从源代码编译了如果你在某个平台上使用非缺省的编译器如在 Windows 上或 macOS 上使用 GCC一般就需要自己编译了具体步骤请参见 Boost 的文档。不过很多 Boost 库是完全不需要编译的只需要把头文件加到编译器能找到的路径里就可以——如我们上一讲讨论的 Boost.Multiprecision 就是这样。我们讨论 Boost 库的时候也会提一下使用这个库是否需要链接某个 Boost 库——需要的话也就意味着需要编译和安装这个 Boost 库。Boost.TypeIndexTypeIndex 是一个很轻量级的库它不需要链接解决的也是使用模板时的一个常见问题如何精确地知道一个表达式或变量的类型。我们还是看一个例子#include iostream #include typeinfo #include utility #include vector #include boost/type_index.hpp using namespace std; using boost::typeindex::type_id; using boost::typeindex:: type_id_with_cvr; int main() { vectorint v; auto it v.cbegin(); cout *** Using typeid\n; cout typeid(const int).name() endl; cout typeid(v).name() endl; cout typeid(it).name() endl; cout *** Using type_id\n; cout type_idconst int() endl; cout type_iddecltype(v)() endl; cout type_iddecltype(it)() endl; cout *** Using type_id_with_cvr\n; cout type_id_with_cvrconst int() endl; cout type_id_with_cvrdecltype( (v))() endl; cout type_id_with_cvrdecltype( move((v)))() endl; cout type_id_with_cvrdecltype( (it))() endl; }上面的代码里展示了标准的 typeid 和 Boost 的 type_id 和 type_id_with_cvr 的使用。它们的区别是typeid 是标准 C 的关键字可以应用到变量或类型上返回一个 std::type_info。我们可以用它的 name 成员函数把结果转换成一个字符串但标准不保证这个字符串的可读性和唯一性。type_id 是 Boost 提供的函数模板必须提供类型作为模板参数——所以对于表达式和变量我们需要使用 decltype。结果可以直接输出到 IO 流上。type_id_with_cvr 和 type_id 相似但它获得的结果会包含 const/volatile 状态及引用类型。上面程序在 MSVC 下的输出为在 GCC 下的输出为我们可以看到 MSVC 下 typeid 直接输出了比较友好的类型名称但 GCC 下没有。此外我们可以注意到typeid 的输出忽略了 const 修饰也不能输出变量的引用类型。type_id 保证可以输出友好的类型名称输出时也不需要调用成员函数但例子里它忽略了 int 的 const 修饰也和 typeid 一样不能输出表达式的引用类型。type_id_with_cvr 可以输出 const/volatile 状态和引用类型注意这种情况下模板参数必须包含引用类型所以我用了 decltype((v)) 这种写法而不是 decltype(v)。如果你忘了这两者的区别请复习一下的 decltype。显然除非你正在使用 MSVC否则调试期 typeid 的用法完全应该用 Boost 的 type_id 来替代。另外如果你的开发环境要求禁用 RTTI运行时类型识别那 typeid 在 Clang 和 GCC 下根本不能使用而使用 Boost.TypeIndex 库仍然没有问题。当然上面说的前提都是你在调试中试图获得变量的类型而不是要获得一个多态对象的运行时类型。后者还是离不开 RTTI 的——虽然你也可以用一些其他方式来模拟 RTTI但我个人觉得一般的项目不太有必要这样做。下面的代码展示了 typeid 和 type_id 在获取对象类型上的差异#include iostream #include typeinfo #include boost/type_index.hpp using namespace std; using boost::typeindex::type_id; class shape { public: virtual ~shape() {} }; class circle : public shape {}; #define CHECK_TYPEID(object, type) \ cout typeid( #object ) \ (typeid(object) \ typeid(type) \ ? is \ : is NOT ) \ #type endl #define CHECK_TYPE_ID(object, \ type) \ cout type_id( #object \ ) \ (type_iddecltype( \ object)() \ type_idtype() \ ? is \ : is NOT ) \ #type endl int main() { shape* ptr new circle(); CHECK_TYPEID(*ptr, shape); CHECK_TYPEID(*ptr, circle); CHECK_TYPE_ID(*ptr, shape); CHECK_TYPE_ID(*ptr, circle); delete ptr; }Boost.CoreCore 里面提供了一些通用的工具这些工具常常被 Boost 的其他库用到而我们也可以使用不需要链接任何库。在这些工具里有些已经可能经过一些变化后进入了 C 标准如addressof在即使用户定义了 operator 时也能获得对象的地址enable_if这个我们已经深入讨论过了is_same判断两个类型是否相同C11 开始在 中定义ref和标准库的相同我们在讨论线程时用过我们在剩下的里面来挑几个讲讲。boost::core::demangleboost::core::demangle 能够用来把 typeid 返回的内部名称“反粉碎”demangle成可读的形式看代码和输出应该就非常清楚了#include iostream #include typeinfo #include vector #include boost/core/demangle.hpp using namespace std; using boost::core::demangle; int main() { vectorint v; auto it v.cbegin(); cout *** Using typeid\n; cout typeid(const int).name() endl; cout typeid(v).name() endl; cout typeid(it).name() endl; cout *** Demangled\n; cout demangle(typeid(const int) .name()) endl; cout demangle(typeid(v).name()) endl; cout demangle( typeid(it).name()) endl; }GCC 下的输出为如果你不使用 RTTI 的话那直接使用 TypeIndex 应该就可以。如果你需要使用 RTTI、又不是只使用 MSVC 的话demangle 就会给你不少帮助。boost::noncopyableboost::noncopyable 提供了一种非常简单也很直白的把类声明成不可拷贝的方式。比如我们里的 shape_wrapper用下面的写法就明确表示了它不允许被拷贝#include boost/core/noncopyable.hpp class shape_wrapper : private boost::noncopyable { … };你当然也可以自己把拷贝构造和拷贝赋值函数声明成 delete不过上面的写法是不是可读性更佳boost::swap你有没有印象在通用的代码如何对一个不知道类型的对象执行交换操作不记得的话标准做法是这样的{ using std::swap; swap(lhs, rhs); }即我们需要在某个小作用域里引入 std::swap然后让编译器在“看得到” std::swap 的情况下去编译 swap 指令。根据 ADL如果在被交换的对象所属类型的名空间下有 swap 函数那个函数会被优先使用否则编译器会选择通用的 std::swap。似乎有点小啰嗦。使用 Boost 的话你可以一行搞定boost::swap(lhs, rhs);当然你需要包含头文件 。Boost.ConversionConversion 同样是一个不需要链接的轻量级的库。它解决了标准 C 里的另一个问题标准类型之间的转换不够方便。在 C11 之前这个问题尤为严重。在 C11 里标准引入了一系列的函数已经可以满足常用类型之间的转换。但使用 Boost.Conversion 里的 lexical_cast 更不需要去查阅方法名称或动脑子去努力记忆。下面是一个例子#include iostream #include stdexcept #include string #include boost/lexical_cast.hpp using namespace std; using boost::bad_lexical_cast; using boost::lexical_cast; int main() { // 整数到字符串的转换 int d 42; auto d_str lexical_caststring(d); cout d_str endl; // 字符串到浮点数的转换 auto f lexical_castfloat(d_str) / 4.0; cout f endl; // 测试 lexical_cast 的转换异常 try { int t lexical_castint(x); cout t endl; } catch (bad_lexical_cast e) { cout e.what() endl; } // 测试标准库 stoi 的转换异常 try { int t std::stoi(x); cout t endl; } catch (invalid_argument e) { cout e.what() endl; } }GCC 下的输出为我觉得 GCC 里 stoi 的异常输出有点太言简意赅了……而 lexical_cast 的异常输出在不同的平台上有很好的一致性。Boost.ScopeExit我们说过 RAII 是推荐的 C 里管理资源的方式。不过作为 C 程序员跟 C 函数打交道也很正常。每次都写个新的 RAII 封装也有点浪费。Boost 里提供了一个简单的封装你可以从下面的示例代码里看到它是如何使用的#include stdio.h #include boost/scope_exit.hpp void test() { FILE* fp fopen(test.cpp, r); if (fp NULL) { perror(Cannot open file); } BOOST_SCOPE_EXIT(fp) { if (fp) { fclose(fp); puts(File is closed); } } BOOST_SCOPE_EXIT_END puts(Faking an exception); throw 42; } int main() { try { test(); } catch (int) { puts(Exception received); } }唯一需要说明的可能就是 BOOST_SCOPE_EXIT 里的那个 符号了——把它理解成 lambda 表达式的按引用捕获就对了虽然 BOOST_SCOPE_EXIT 可以支持 C98 的代码。如果不需要捕获任何变量BOOST_SCOPE_EXIT 的参数必须填为 void。输出为假设 test.cpp 存在使用这个库也只需要头文件。注意实现类似的功能在 C11 里相当容易但由于 ScopeExit 可以支持 C98 的代码因而它的实现还是相当复杂的。Boost.Program_options传统上 C 代码里处理命令行参数会使用 getopt。我也用过比如在下面的代码中breaktext/breaktext.c at master · adah1972/breaktext · GitHub这种方式有不少缺陷一个选项通常要在三个地方重复说明文本里getopt 的参数里以及对 getopt 的返回结果进行处理时。不知道你觉得怎样我反正发生过改了一处、漏改其他的错误。对选项的附加参数需要手工写代码处理因而常常不够严格C 的类型转换不够方便尤其是检查错误。Program_options 正是解决这个问题的。这个代码有点老了不过还挺实用懒得去找特别的处理库时至少这个伸手可用。使用这个库需要链接 boost_program_options 库。下面的代码展示了代替上面的 getopt 用法的代码#include iostream #include string #include stdlib.h #include boost/program_options.hpp namespace po boost::program_options; using std::cout; using std::endl; using std::string; string locale; string lang; int width 72; bool keep_indent false; bool verbose false; int main(int argc, char* argv[]) { po::options_description desc( Usage: breaktext [OPTION]... Input File [Output File]\n \n Available options); desc.add_options() (locale,L, po::valuestring(locale), Locale of the console (system locale by default)) (lang,l, po::valuestring(lang), Language of input (asssume no language by default)) (width,w, po::valueint(width), Width of output text (72 by default)) (help,h, Show this help message and exit) (,i, po::bool_switch(keep_indent), Keep space indentation) (,v, po::bool_switch(verbose), Be verbose); po::variables_map vm; try { po::store( po::parse_command_line( argc, argv, desc), vm); } catch (po::error e) { cout e.what() endl; exit(1); } vm.notify(); if (vm.count(help)) { cout desc \n; exit(1); } }这样我们的程序就能处理上面的那些选项了。如果运行时在命令行加上 -h 或 --help 选项程序就会输出跟原来类似的帮助输出——额外的好处是选项的描述信息较长时还能自动帮你折行不需要手工排版了。建议你自己尝试一下提供各种正确或错误的选项来检查一下运行的结果。当然现在有些更新的选项处理库但它们应该都和 Program_options 更接近而不是和 getopt 更接近。如果你感觉 Program_options 功能不足了换一个其他库不会是件麻烦事。Boost.HanaBoost 里自然也有模板元编程相关的东西。但我不打算介绍 MPL、Fusion 和 Phoenix 那些因为有些技巧在 C11 和 Lambda 表达式到来之后已经略显得有点过时了。Hana 则不同它是一个使用了 C11/14 实现技巧和惯用法的新库也和一般的模板库一样只要有头文件就能使用。Hana 里定义了一整套供编译期使用的数据类型和函数。我们现在看一下它提供的部分类型type把类型转化成对象我们在曾经示例过相反的动作把数值转化成对象来方便后续处理。integral_constant跟 std::integral_constant 相似但定义了更多的运算符和语法糖。特别的你可以用字面量来生成一个 long long 类型的 integral_constant如 1_c。string一个编译期使用的字符串类型。tuple跟 std::tuple 类似意图是当作编译期的 vector 来使用。map编译期使用的关联数组。set编译期使用的集合。Hana 里的算法的名称跟标准库的类似我就不一一列举了。下面的例子展示了一个基本用法#include boost/hana.hpp namespace hana boost::hana; class shape {}; class circle {}; class triangle {}; int main() { using namespace hana::literals; constexpr auto tup hana::make_tuple( hana::type_cshape*, hana::type_ccircle, hana::type_ctriangle); constexpr auto no_pointers hana::remove_if( tup, [](auto a) { return hana::traits:: is_pointer(a); }); static_assert( no_pointers hana::make_tuple( hana::type_ccircle, hana::type_ctriangle)); static_assert( hana::reverse(no_pointers) hana::make_tuple( hana::type_ctriangle, hana::type_ccircle)); static_assert( tup[1_c] hana::type_ccircle); }这个程序可以编译但没有任何运行输出。在这个程序里我们做了下面这几件事使用 type_c 把类型转化成 type 对象并构造了类型对象的 tuple使用 remove_if 算法移除了 tup 中的指针类型使用静态断言确认了结果是我们想要的使用静态断言确认了可以用 reverse 把 tup 反转一下使用静态断言确认了可以用方括号运算符来获取 tup 中的某一项可以看到Hana 本质上以类似普通的运行期编程的写法来做编译期的计算。上面展示的只是一些最基本的用法而 Hana 的文档里展示了很多有趣的用法。尤其值得一看的是文档中展示了如何利用 Hana 提供的机制来自己定义 switch_、case_、default_使得下面的代码可以通过编译boost::any a x; std::string r switch_(a)( case_int([](auto i) { return int: s std::to_string(i); }), case_char([](auto c) { return char: s std::string{c}; }), default_( [] { return unknowns; })); assert(r char: xs);我个人认为很有意思。内容小结本讲我们对 Boost 的意义做了概要介绍并蜻蜓点水地简单描述了若干 Boost 库的功能。如果你想进一步了解 Boost 的细节的话就得自行查看文档了。