在本章中,我们将介绍:
- 获取配置选项
- 在容器/变量中存储任何值
- 在容器/变量中存储多个选择的类型
- 使用更安全的方法来处理存储多个选定类型的容器
- 如果没有值,则返回一个值或标志
- 从函数返回数组
- 将多个值组合成一个
- 绑定和重新排序函数参数
- 获取人类可读的类型名
- 使用 C++ 11 移动仿真
- 制作不可复制的类
- 创建一个不可复制但可移动的类
- 使用 C++ 14 和 C++ 11 算法
Boost 是 C++ 库的集合。每个库在被 Boost 接受之前都经过了很多专业程序员的审核。使用许多编译器和许多 C++ 标准库实现在多个平台上测试库。使用 Boost 时,您可以确信您使用的是最便携、最快速、最可靠的解决方案之一,该解决方案是根据适用于商业和开源项目的许可证分发的。
Boost 的许多部分已经包含在 C++ 11、C++ 14 和 C++ 17 中。此外,Boost 库将包含在下一个 C++ 标准中。你会在这本书的每个食谱中找到 C++ 标准特定的注释。
没有长篇大论的介绍,我们开始吧!
在本章中,我们将看到一些日常使用的食谱。我们将看到如何从不同的来源获得配置选项,以及使用 Boost 库作者引入的一些数据类型可以实现什么。
来看看一些控制台程序,比如 Linux 中的cp。他们都有一个奇特的帮助;它们的输入参数不依赖于任何位置,并且具有人类可读的语法。例如:
$ cp --help
Usage: cp [OPTION]... [-T] SOURCE DEST
-a, --archive same as -dR --preserve=all
-b like --backup but does not accept an argument 您可以在 10 分钟内为您的程序实现相同的功能。你所需要的只是Boost.ProgramOptions图书馆。
这个食谱只需要 C++ 的基本知识。请记住,这个库不仅仅是一个头,所以你的程序必须链接到libboost_program_options库。
让我们从一个简单的程序开始,该程序接受apples和oranges的计数作为输入,并计算水果的总数。我们希望实现以下结果:
$ ./our_program.exe --apples=10 --oranges=20 Fruits count: 30请执行以下步骤:
- 包含
boost/program_options.hpp头,为boost::program_options命名空间做一个别名(太长了,打不出来!).我们还需要一个<iostream>标题:
#include <boost/program_options.hpp>
#include <iostream>
namespace opt = boost::program_options; - 现在,我们准备在
main()功能中描述我们的选项:
int main(int argc, char *argv[])
{
// Constructing an options describing variable and giving
// it a textual description "All options".
opt::options_description desc("All options");
// When we are adding options, first parameter is a name
// to be used in command line. Second parameter is a type
// of that option, wrapped in value<> class. Third parameter
// must be a short description of that option.
desc.add_options()
("apples", opt::value<int>(), "how many apples do
you have")
("oranges", opt::value<int>(), "how many oranges do you
have")
("help", "produce help message")
;- 让我们解析命令行:
// Variable to store our command line arguments.
opt::variables_map vm;
// Parsing and storing arguments.
opt::store(opt::parse_command_line(argc, argv, desc), vm);
// Must be called after all the parsing and storing.
opt::notify(vm);- 让我们添加一些代码来处理
help选项:
if (vm.count("help")) {
std::cout << desc << "\n";
return 1;
}- 最后一步。水果计数可以通过以下方式实现:
std::cout << "Fruits count: "
<< vm["apples"].as<int>() + vm["oranges"].as<int>()
<< std::endl;
} // end of `main`现在,如果我们用help参数调用我们的程序,我们将得到以下输出:
All options:
--apples arg how many apples do you have
--oranges arg how many oranges do you have
--help produce help message 如您所见,我们没有为help选项的值提供类型,因为我们不期望任何值被传递给它。
这个例子很容易从代码和注释中理解。运行它会产生预期的结果:
$ ./our_program.exe --apples=100 --oranges=20 Fruits count: 120C++ 标准采用了很多 Boost 库;然而,即使在 C++ 17 中也找不到Boost.ProgramOptions。目前还没有计划将其采用到 C++ 2a 中。
ProgramOptions库非常强大,有很多功能。以下是如何:
- 将配置选项值直接解析为一个变量,并使该选项成为必需选项:
int oranges_var = 0;
desc.add_options()
// ProgramOptions stores the option value into
// the variable that is passed by pointer. Here value of
// "--oranges" option will be stored into 'oranges_var'.
("oranges,o", opt::value<int>(&oranges_var)->required(),
"oranges you have")- 获取一些强制字符串选项:
// 'name' option is not marked with 'required()',
// so user may not provide it.
("name", opt::value<std::string>(), "your name")- 添加苹果的简称,将
10设为apples的默认值:
// 'a' is a short option name for apples. Use as '-a 10'.
// If no value provided, then the default value is used.
("apples,a", opt::value<int>()->default_value(10),
"apples that you have");- 从配置文件中获取缺少的选项:
opt::variables_map vm;
// Parsing command line options and storing values to 'vm'.
opt::store(opt::parse_command_line(argc, argv, desc), vm);
// We can also parse environment variables. Just use
// 'opt::store with' 'opt::parse_environment' function.
// Adding missing options from "apples_oranges.cfg" config file.
try {
opt::store(
opt::parse_config_file<char>("apples_oranges.cfg", desc),
vm
);
} catch (const opt::reading_file& e) {
std::cout << "Error: " << e.what() << std::endl;
}The configuration file syntax differs from the command-line syntax. We do not need to place minuses before the options. So, our apples_oranges.cfg file must look like this:
oranges=20
- 验证是否设置了所有必需的选项:
try {
// `opt::required_option` exception is thrown if
// one of the required options was not set.
opt::notify(vm);
} catch (const opt::required_option& e) {
std::cout << "Error: " << e.what() << std::endl;
return 2;
}如果我们将所有提到的提示组合成一个可执行文件,那么它的help命令将产生以下输出:
$ ./our_program.exe --help
All options:
-o [ --oranges ] arg oranges that you have
--name arg your name
-a [ --apples ] arg (=10) apples that you have
--help produce help message
在没有配置文件的情况下运行它将产生以下输出:
$ ./our_program.exe
Error: can not read options configuration file 'apples_oranges.cfg'
Error: the option '--oranges' is required but missing 用配置文件中的oranges=20运行程序会生成++,因为苹果的默认值是10:
$ ./our_program.exe
Fruits count: 30- Boost 的官方文档包含了更多的例子,告诉我们
Boost.ProgramOptions更高级的特性,比如位置相关选项、非常规语法等等;这在http://boost.org/libs/program_options有售 - 您可以在http://apolukhin.github.io/Boost-Cookbook在线修改并运行本书中的所有示例
如果你一直在用 Java、C#或 Delphi 编程,你肯定会错过用 C++ 中的Object值类型创建容器的能力。这些语言中的Object类是几乎所有类型的基本类,因此您可以随时为它赋值。想象一下,在 C++ 中拥有这样一个特性有多好:
typedef std::unique_ptr<Object> object_ptr;
std::vector<object_ptr> some_values;
some_values.push_back(new Object(10));
some_values.push_back(new Object("Hello there"));
some_values.push_back(new Object(std::string("Wow!")));
std::string* p = dynamic_cast<std::string*>(some_values.back().get());
assert(p);
(*p) += " That is great!\n";
std::cout << *p; 我们将使用只有标题的库。这个食谱只需要 C++ 的基本知识。
Boost 提供了一个解决方案Boost.Any库,它有更好的语法:
#include <boost/any.hpp>
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<boost::any> some_values;
some_values.push_back(10);
some_values.push_back("Hello there!");
some_values.push_back(std::string("Wow!"));
std::string& s = boost::any_cast<std::string&>(some_values.back());
s += " That is great!";
std::cout << s;
} 很棒,不是吗?顺便说一下,它有一个空状态,可以使用empty()成员函数来检查(就像在标准库容器中一样)。
您可以使用两种方法从boost::any获得该值:
void example() {
boost::any variable(std::string("Hello world!"));
// Following method may throw a boost::bad_any_cast exception
// if actual value in variable is not a std::string.
std::string s1 = boost::any_cast<std::string>(variable);
// Never throws. If actual value in variable is not a std::string
// will return an NULL pointer.
std::string* s2 = boost::any_cast<std::string>(&variable);
}boost::any类只是在其中存储任何值。为了实现这一点,它使用了类型擦除技术(接近于 Java 或 C#对所有类型的做法)。要使用这个库,你不需要知道它的内部实现细节,但是这里有一个好奇者类型擦除技术的快速浏览。
在分配类型为T的某个变量时,Boost.Any实例化了一个holder<T>类型,该类型可以存储指定类型T的值,并且是从某个基类型placeholder中派生出来的:
template<typename ValueType>
struct holder : public placeholder {
virtual const std::type_info& type() const {
return typeid(ValueType);
}
ValueType held;
};一个placeholder类型具有虚拟功能,用于获取一个存储类型T的std::type_info和克隆一个存储类型:
struct placeholder {
virtual ~placeholder() {}
virtual const std::type_info& type() const = 0;
};boost::any存储ptr -指向placeholder的指针。使用any_cast<T>()时,boost::any检查调用ptr->type()使std::type_info等于typeid(T)并返回static_cast<holder<T>*>(ptr)->held。
这种灵活性从来不是没有代价的。复制构造、值构造、复制赋值、赋值给boost::any的实例进行动态内存分配;所有类型转换都进行运行时类型信息 ( RTTI )检查;boost::any大量使用虚函数。如果你热衷于性能,下一个食谱会告诉你如何在没有动态分配和 RTTI 使用的情况下获得几乎相同的结果。
boost::any使用右值引用,但不能在常量中使用。
Boost.Any库被 C++ 17 接受。如果您的编译器兼容 C++ 17,并且您希望避免对any使用 Boost,只需将boost命名空间替换为命名空间std,并包含<any>而不是<boost/any.hpp>。如果您在std::any中存储微小的对象,您的标准库实现可能会稍微快一些。
std::any has the reset() function instead of clear() and has_value() instead of empty(). Almost all exceptions in Boost derived from the std::exception class or from its derivatives, for example, boost::bad_any_cast is derived from std::bad_cast. It means that you can catch almost all Boost exceptions using catch (const std::exception& e).
- Boost 的官方文档可能会给你更多的例子;可以在http://boost.org/libs/any找到
- 使用一种更安全的方法来处理存储多种选择类型的容器配方,以获得关于该主题的更多信息
C++ 03 联合只能持有名为普通旧数据 ( POD )的极其简单的类型。例如在 C++ 03 中,不能将std::string或std::vector存储在并集中。
你知道 C++ 11 中无限制并集的概念吗?让我简单地告诉你这件事。C++ 11 放宽了对联合的要求,但是你必须自己管理非 POD 类型的构造和销毁。您必须调用就地构建/销毁,并记住存储在联合中的类型。工作量很大,不是吗?
我们能在 C++ 03 中有一个像变量一样的不受限制的联合来管理对象的生存期并记住它的类型吗?
我们将使用简单易用的仅头库。这个食谱只需要 C++ 的基本知识。
让我给你介绍一下Boost.Variant图书馆。
Boost.Variant库可以存储编译时指定的任何类型。它还管理就地构建/销毁,甚至不需要 C++ 11 标准:
#include <boost/variant.hpp>
#include <iostream>
#include <vector>
#include <string>
int main() {
typedef boost::variant<int, const char*, std::string> my_var_t;
std::vector<my_var_t> some_values;
some_values.push_back(10);
some_values.push_back("Hello there!");
some_values.push_back(std::string("Wow!"));
std::string& s = boost::get<std::string>(some_values.back());
s += " That is great!\n";
std::cout << s;
} 很棒,不是吗?
Boost.Variant没有空状态,但是有一个empty()功能,没有用,总是返回false。如果需要表示空状态,只需在Boost.Variant库支持的类型的第一个位置添加一些简单的类型即可。当Boost.Variant包含该类型时,将其解释为空状态。这里有一个例子,我们将使用boost::blank类型来表示空状态:
void example1() {
// Default constructor constructs an instance of boost::blank.
boost::variant<
boost::blank, int, const char*, std::string
> var;
// 'which()' method returns an index of a type
// currently held by variant.
assert(var.which() == 0); // boost::blank
var = "Hello, dear reader";
assert(var.which() != 0);
}- 您可以使用两种方法从变量中获取值:
void example2() {
boost::variant<int, std::string> variable(0);
// Following method may throw a boost::bad_get
// exception if actual value in variable is not an int.
int s1 = boost::get<int>(variable);
// If actual value in variable is not an int will return NULL.
int* s2 = boost::get<int>(&variable);
}boost::variant类保存一个字节数组,并将值存储在该数组中。数组的大小在编译时通过应用sizeof()和函数来确定,以便与每个模板类型对齐。在赋值或构造boost::variant时,先前的值被就地析构,新的值被构造在字节数组的顶部,使用新的位置。
Boost.Variant变量通常不动态分配内存,也不需要启用 RTTI。Boost.Variant速度极快,被其他 Boost 库广泛使用。为了获得最大性能,请确保在第一个位置的支持类型列表中有一个简单类型。boost::variant利用 C++ 11 的右值引用,如果它们在你的编译器上可用的话。
Boost.Variant是 C++ 17 标准的一部分。std::variant与boost::variant略有不同:
std::variant在<variant>头文件中声明,而不是在<boost.variant.hpp>中声明std::variant从不分配内存std::variant可与 constexpr 一起使用- 不是写
boost::get<int>(&variable),你得为std::variant写std::get_if<int>(&variable) std::variant不能递归保持自身,错过了一些其他高级技术std::variant可以原地构造物体std::variant有index()而不是which()
- 使用一种更安全的方法来处理储存多种选择类型的配方的容器
- Boost 的官方文档包含了更多关于
Boost.Variant其他一些特性的例子和描述,可以在:http://boost.org/libs/variant找到 - 在http://apolukhin.github.io/Boost-Cookbook在线试验代码
假设您正在为某个 SQL 数据库接口创建一个包装器。您决定boost::any将完美地匹配数据库表的单个单元格的要求。
其他一些程序员将使用您的类,他/她的任务是从数据库中获取一行,并计算一行中算术类型的总和。
这就是这样一个代码的样子:
#include <boost/any.hpp>
#include <vector>
#include <string>
#include <typeinfo>
#include <algorithm>
#include <iostream>
// This typedefs and methods will be in our header,
// that wraps around native SQL interface.
typedef boost::any cell_t;
typedef std::vector<cell_t> db_row_t;
// This is just an example, no actual work with database.
db_row_t get_row(const char* /*query*/) {
// In real application 'query' parameter shall have a 'const
// char*' or 'const std::string&' type? See recipe "Type
// 'reference to string'" for an answer.
db_row_t row;
row.push_back(10);
row.push_back(10.1f);
row.push_back(std::string("hello again"));
return row;
}
// This is how a user will use your classes
struct db_sum {
private:
double& sum_;
public:
explicit db_sum(double& sum)
: sum_(sum)
{}
void operator()(const cell_t& value) {
const std::type_info& ti = value.type();
if (ti == typeid(int)) {
sum_ += boost::any_cast<int>(value);
} else if (ti == typeid(float)) {
sum_ += boost::any_cast<float>(value);
}
}
};
int main() {
db_row_t row = get_row("Query: Give me some row, please.");
double res = 0.0;
std::for_each(row.begin(), row.end(), db_sum(res));
std::cout << "Sum of arithmetic types in database row is: "
<< res << std::endl;
} 如果您编译并运行这个示例,它将输出一个正确的答案:
Sum of arithmetic types in database row is: 20.1你还记得自己在阅读operator()的实现时的想法吗?我猜他们是,*“那么双,长,短,无符号,和其他类型呢?”*同样的想法会出现在使用你的界面的程序员的脑海中。因此,您需要仔细记录由您的cell_t存储的值,或者使用更优雅的解决方案,如下节所述。
如果您还不熟悉Boost.Variant和Boost.Any库,强烈建议阅读前面两个食谱。
Boost.Variant库实现了访问存储数据的访问者编程模式,这比通过boost::get<>获取值安全得多。这种模式迫使程序员处理变体中的每一种类型,否则代码将无法编译。您可以通过boost::apply_visitor功能使用该模式,该功能将visitor功能对象作为第一个参数,将variant作为第二个参数。如果您使用的是预 C++ 14 编译器,那么visitor函数对象必须从boost::static_visitor<T>类派生,其中T是由visitor返回的类型。对于变量存储的每种类型,一个visitor对象必须有operator()的重载。
让我们将cell_t类型更改为boost::variant<int, float, string>并修改我们的示例:
#include <boost/variant.hpp>
#include <vector>
#include <string>
#include <iostream>
// This typedefs and methods will be in header,
// that wraps around native SQL interface.
typedef boost::variant<int, float, std::string> cell_t;
typedef std::vector<cell_t> db_row_t;
// This is just an example, no actual work with database.
db_row_t get_row(const char* /*query*/) {
// See recipe "Type 'reference to string'"
// for a better type for 'query' parameter.
db_row_t row;
row.push_back(10);
row.push_back(10.1f);
row.push_back("hello again");
return row;
}
// This is a code required to sum values.
// We can provide no template parameter
// to boost::static_visitor<> if our visitor returns nothing.
struct db_sum_visitor: public boost::static_visitor<double> {
double operator()(int value) const {
return value;
}
double operator()(float value) const {
return value;
}
double operator()(const std::string& /*value*/) const {
return 0.0;
}
};
int main() {
db_row_t row = get_row("Query: Give me some row, please.");
double res = 0.0;
for (auto it = row.begin(), end = row.end(); it != end; ++ it) {
res += boost::apply_visitor(db_sum_visitor(), *it);
}
std::cout << "Sum of arithmetic types in database row is: "
<< res << std::endl;
}在编译时,Boost.Variant库生成一个大的switch语句,每个语句从变量的类型列表中为一个类型调用一个visitor。运行时,使用which()检索存储类型的索引,并跳转到switch语句中的正确案例。将为boost::variant<int, float, std::string>生成类似这样的内容:
switch (which())
{
case 0 /*int*/:
return visitor(*reinterpret_cast<int*>(address()));
case 1 /*float*/:
return visitor(*reinterpret_cast<float*>(address()));
case 2 /*std::string*/:
return visitor(*reinterpret_cast<std::string*>(address()));
default: assert(false);
} 这里,address()函数返回一个指向boost::variant<int, float, std::string>内部存储器的指针。
如果我们将这个例子与这个食谱中的第一个例子进行比较,我们会看到boost::variant的以下优点:
- 我们知道变量可以存储哪些类型
- 如果一个 SQL 接口的库编写器添加或修改了一个
variant持有的类型,我们会得到一个编译时错误,而不是不正确的行为
来自 C++ 17 的std::variant也支持访问。只写std::visit而不是boost::apply_visitor就完事了。
You can download the example code files for all Packt books that you have purchased from your account at http://www.PacktPub.com. If you purchased this book elsewhere, you can visit http://www.PacktPub.com/support, and register to have the files emailed directly to you.
- 在阅读了第 4 章、编译时技巧中的一些方法后,即使底层类型发生变化,您也能够制作出正常工作的通用
visitor对象 - Boost 的官方文档包含了更多的例子和对
Boost.Variant其他一些特性的描述;可通过以下链接获得:http://boost.org/libs/variant
想象一下,我们有一个函数,它不会抛出异常并返回一个值或指示发生了错误。在 Java 或 C#编程语言中,这种情况是通过比较函数值的返回值和null指针来处理的。如果功能返回null,则出现错误。在 C++ 中,从函数返回指针会让库用户感到困惑,并且通常需要缓慢的动态内存分配。
这个食谱只需要 C++ 的基础知识。
女士们先生们,让我用下面的例子向你们介绍Boost.Optional图书馆:
try_lock_device()函数试图获取一个设备的锁,并可能成功或失败,这取决于不同的条件(在我们的例子中,它取决于一些try_lock_device_impl()函数调用):
#include <boost/optional.hpp>
#include <iostream>
class locked_device {
explicit locked_device(const char* /*param*/) {
// We have unique access to device.
std::cout << "Device is locked\n";
}
static bool try_lock_device_impl();
public:
void use() {
std::cout << "Success!\n";
}
static boost::optional<locked_device> try_lock_device() {
if (!try_lock_device_impl()) {
// Failed to lock device.
return boost::none;
}
// Success!
return locked_device("device name");
}
~locked_device(); // Releases device lock.
};该函数返回可转换为bool的boost::optional变量。如果返回值等于true,则获取锁,并且可以通过对返回的可选变量解引用来获得使用该设备的类的实例:
int main() {
for (unsigned i = 0; i < 10; ++ i) {
boost::optional<locked_device> t
= locked_device::try_lock_device();
// optional is convertible to bool.
if (t) {
t->use();
return 0;
} else {
std::cout << "...trying again\n";
}
}
std::cout << "Failure!\n";
return -1;
} 该程序将输出以下内容:
...trying again
...trying again
Device is locked
Success!The default constructed optional variable is convertible to false and must not be dereferenced, because such an optional does not have an underlying type constructed.
引擎盖下的boost::optional<T>有一个正确对齐的字节数组,其中T类型的对象可以是就地构造的。它还有一个bool变量来记住物体的状态(它是否被构造?).
Boost.Optional类不使用动态分配,也不需要基础类型的默认构造函数。当前的boost::optional实现可以使用 C++ 11 右值引用,但是不能用于 constexpr。
If you have a class T that has no empty state but your program logic requires an empty state or uninitialized T, then you have to come up with some workaround. Traditionally, users create some smart pointer to the class T, keep a nullptr in it, and dynamically allocate T if non empty state is required. Stop doing that! Use boost::optional<T> instead. It's a much faster and more reliable solution.
C++ 17 标准包括std::optional类。只需将<boost/optional.hpp>替换为<optional>,将boost::替换为std::即可使用该类的标准版本。std::optional可与 constexpr 一起使用。
Boost 的官方文档包含了更多的例子,并描述了Boost.Optional的高级特性(比如就地建造)。该文档可通过以下链接获得:http://boost.org/libs/optional.
让我们玩一个猜谜游戏!关于下面的功能,你能说些什么?
char* vector_advance(char* val); 程序员是否应该释放返回值?该函数是否试图取消分配输入参数?输入参数应该以零结尾,还是函数应该假设输入参数具有指定的宽度?
现在,让我们把任务变得更难!请看下面一行:
char ( &vector_advance( char (&val)[4] ) )[4];别担心。在了解这里发生的事情之前,我也挠了半个小时的头。vector_advance是接受并返回一个四元素数组的函数。有没有办法把这样的函数写清楚?
这个食谱只需要 C++ 的基础知识。
我们可以这样重写函数:
#include <boost/array.hpp>
typedef boost::array<char, 4> array4_t;
array4_t& vector_advance(array4_t& val);这里,boost::array<char, 4>只是一个简单的包装器,围绕着一个由四个char元素组成的数组。
这段代码回答了我们第一个示例中的所有问题,并且比第二个示例中的代码可读性更好。
boost::array是固定大小的数组。boost::array的第一个模板参数是元素类型,第二个是数组的大小。如果需要在运行时更改数组大小,请改用std::vector、boost::container::small_vector、boost::container::stack_vector或boost::container::vector。
boost::array<>类没有手写的构造函数,它的所有成员都是公共的,所以编译器会将其视为 POD 类型。
我们再来看一些boost::array用法的例子:
#include <boost/array.hpp>
#include <algorithm>
typedef boost::array<char, 4> array4_t;
array4_t& vector_advance(array4_t& val) {
// C++ 11 lambda function
const auto inc = [](char& c){ ++ c; };
// boost::array has begin(), cbegin(), end(), cend(),
// rbegin(), size(), empty() and other functions that are
// common for standard library containers.
std::for_each(val.begin(), val.end(), inc);
return val;
}
int main() {
// We can initialize boost::array just like an array in C++ 11:
// array4_t val = {0, 1, 2, 3};
// but in C++ 03 additional pair of curly brackets is required.
array4_t val = {{0, 1, 2, 3}};
array4_t val_res; // it is default constructible
val_res = vector_advance(val); // it is assignable
assert(val.size() == 4);
assert(val[0] == 1);
/*val[4];*/ // Will trigger an assert because max index is 3
// We can make this assert work at compile-time.
// Interested? See recipe 'Check sizes at compile-time'
assert(sizeof(val) == sizeof(char) * array4_t::static_size);
} boost::array最大的优点之一是不分配动态内存,提供与普通 C 数组完全相同的性能。C++ 标准委员会的人也喜欢它,所以它被接受为 C++ 11 标准。尝试包括<array>标题,并检查std::array的可用性。std::array自 C++ 17 以来,对 constexpr 的使用有了更好的支持。
- Boost 的官方文档给出了
Boost.Array方法的完整列表,并描述了方法的复杂性和抛出行为。可在以下链接获得:http://boost.org/libs/array. boost::array功能广泛应用于各种食谱;例如,参考绑定一个值作为功能参数配方。
有一份非常好的礼物送给喜欢的人。Boost 有一个名为Boost.Tuple的库。它就像std::pair一样,但是它也可以处理三元组、四元组,甚至更大的类型集合。
这个食谱只需要 C++ 的基础知识和一个标准库。
执行以下步骤将多个值合并为一个:
- 要开始使用元组,您需要包含一个适当的头并声明一个变量:
#include <boost/tuple/tuple.hpp>
#include <string>
boost::tuple<int, std::string> almost_a_pair(10, "Hello");
boost::tuple<int, float, double, int> quad(10, 1.0f, 10.0, 1);- 获取特定值是通过
boost::get<N>()函数实现的,其中N是所需值的从零开始的索引:
#include <boost/tuple/tuple.hpp>
void sample1() {
const int i = boost::get<0>(almost_a_pair);
const std::string& str = boost::get<1>(almost_a_pair);
const double d = boost::get<2>(quad);
}boost::get<>功能有许多过载,在 Boost 中广泛使用。我们已经在中看到了它如何与其他库一起使用,在容器/变量配方中存储多个选择的类型。
- 您可以使用
boost::make_tuple()函数构造元组,该函数编写起来更短,因为您不需要完全限定元组类型:
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_comparison.hpp>
#include <set>
void sample2() {
// Tuple comparison operators are
// defined in header "boost/tuple/tuple_comparison.hpp"
// Don't forget to include it!
std::set<boost::tuple<int, double, int> > s;
s.insert(boost::make_tuple(1, 1.0, 2));
s.insert(boost::make_tuple(2, 10.0, 2));
s.insert(boost::make_tuple(3, 100.0, 2));
// Requires C++ 11
const auto t = boost::make_tuple(0, -1.0, 2);
assert(2 == boost::get<2>(t));
// We can make a compile time assert for type
// of t. Interested? See chapter 'Compile time tricks'
}- 另一个让生活更轻松的功能是
boost::tie()。它几乎和make_tuple一样工作,但是为每个传递的类型添加了一个非引用。这样的元组可以用来从另一个元组中获取变量的值。从下面的例子可以更好地理解:
#include <boost/tuple/tuple.hpp>
#include <cassert>
void sample3() {
boost::tuple<int, float, double, int> quad(10, 1.0f, 10.0, 1);
int i;
float f;
double d;
int i2;
// Passing values from 'quad' variables
// to variables 'i', 'f', 'd', 'i2'.
boost::tie(i, f, d, i2) = quad;
assert(i == 10);
assert(i2 == 1);
}有些读者可能想知道,当我们总是可以用更好的名字编写自己的结构时,为什么我们需要一个元组;例如,我们可以创建一个结构,而不是写boost::tuple<int, std::string>:
struct id_name_pair {
int id;
std::string name;
}; 嗯,这个结构肯定比boost::tuple<int, std::string>清晰。元组库背后的主要思想是简化模板编程。
元组的工作速度与std::pair一样快(它不在堆上分配内存,也没有虚函数)。C++ 委员会发现这个类非常有用,它被包含在标准库中。你可以在头文件<tuple>的 C++ 11 兼容实现中找到它(别忘了用std::替换所有的boost::名称空间)。
tuple 的标准库版本必须具有多个微优化,并且通常提供稍好的用户体验。但是,元组元素的构造顺序没有保证,所以,如果你需要一个从第一个开始构造其元素的元组,你必须使用boost::tuple:
#include <boost/tuple/tuple.hpp>
#include <iostream>
template <int I>
struct printer {
printer() { std::cout << I; }
};
int main() {
// Outputs 012
boost::tuple<printer<0>, printer<1>, printer<2> > t;
}元组的当前 Boost 实现不使用变量模板,不支持右值引用,不支持 C++ 17 结构化绑定,并且不能与 constexpr 一起使用。
- Boost 的官方文档包含了更多的例子,关于
Boost.Tuple的性能和能力的信息。可在链接http://boost.org/libs/tuple获得。 - 第八章、元编程、中的将所有元组元素转换为字符串配方展示了元组的一些高级用法。
如果你经常使用标准库并使用<algorithm>头,你肯定会写很多功能对象。在 C++ 14 中,您可以为此使用泛型 lambdas。在 C++ 11 中,您只有非泛型 lambdas。在 C++ 标准的早期版本中,可以使用bind1st、bind2nd、ptr_fun、mem_fun、mem_fun_ref等适配器函数构造函数对象,也可以手工编写(因为适配器函数看起来很吓人)。这里有一些好消息:Boost.Bind可以用来代替难看的适配器函数,并且它提供了一种更具可读性的语法。
了解标准库函数和算法会有所帮助。
让我们看看一些使用Boost.Bind和 C++ 11 lambda 类的例子:
- 所有示例都需要以下标题:
// Contains boost::bind and placeholders.
#include <boost/bind.hpp>
// Utility stuff required by samples.
#include <boost/array.hpp>
#include <algorithm>
#include <functional>
#include <string>
#include <cassert>- 计数值大于 5,如以下代码所示:
void sample1() {
const boost::array<int, 12> v = {{
1, 2, 3, 4, 5, 6, 7, 100, 99, 98, 97, 96
}};
const std::size_t count0 = std::count_if(v.begin(), v.end(),
[](int x) { return 5 < x; }
);
const std::size_t count1 = std::count_if(v.begin(), v.end(),
boost::bind(std::less<int>(), 5, _1)
);
assert(count0 == count1);
}- 我们可以这样计算空字符串:
void sample2() {
const boost::array<std::string, 3> v = {{
"We ", "are", " the champions!"
}};
const std::size_t count0 = std::count_if(v.begin(), v.end(),
[](const std::string& s) { return s.empty(); }
);
const std::size_t count1 = std::count_if(v.begin(), v.end(),
boost::bind(&std::string::empty, _1)
);
assert(count0 == count1);
} - 现在,让我们计算长度小于
5的字符串:
void sample3() {
const boost::array<std::string, 3> v = {{
"We ", "are", " the champions!"
}};
const std::size_t count0 = std::count_if(v.begin(), v.end(),
[](const std::string& s) { return s.size() < 5; }
);
const std::size_t count1 = std::count_if(v.begin(), v.end(),
boost::bind(
std::less<std::size_t>(),
boost::bind(&std::string::size, _1),
5
)
);
assert(count0 == count1);
} - 比较字符串:
void sample4() {
const boost::array<std::string, 3> v = {{
"We ", "are", " the champions!"
}};
std::string s(
"Expensive copy constructor is called when binding"
);
const std::size_t count0 = std::count_if(v.begin(), v.end(),
[&s](const std::string& x) { return x < s; }
);
const std::size_t count1 = std::count_if(v.begin(), v.end(),
boost::bind(std::less<std::string>(), _1, s)
);
assert(count0 == count1);
} boost::bind函数返回一个函数对象,该函数对象存储绑定值的副本和原始函数对象的副本。当执行对operator()的实际调用时,存储的参数与调用时传递的参数一起传递给原始功能对象。
看看前面的例子。当我们绑定值时,我们将一个值复制到一个函数对象中。对于某些类,这种操作很昂贵。有没有办法绕过抄袭?
是的,有!Boost.Ref图书馆会在这里帮助我们!它包含两个函数,boost::ref()和boost::cref(),第一个函数允许我们传递一个参数作为引用,第二个函数传递参数作为常量引用。ref()和cref()函数只是构造一个类型为reference_wrapper<T>或reference_wrapper<const T>的对象,它可以隐式转换为引用类型。让我们改变最后的例子:
#include <boost/ref.hpp>
void sample5() {
const boost::array<std::string, 3> v = {{
"We ", "are", " the champions!"
}};
std::string s(
"Expensive copy constructor is NOT called when binding"
);
const std::size_t count1 = std::count_if(v.begin(), v.end(),
boost::bind(std::less<std::string>(), _1, boost::cref(s))
);
// ...
} 您也可以使用bind重新排序、忽略和复制功能参数:
void sample6() {
const auto twice = boost::bind(std::plus<int>(), _1, _1);
assert(twice(2) == 4);
const auto minus_from_second = boost::bind(std::minus<int>(), _2, _1);
assert(minus_from_second(2, 4) == 2);
const auto sum_second_and_third = boost::bind(
std::plus<int>(), _2, _3
);
assert(sum_second_and_third(10, 20, 30) == 50);
}函数ref、cref和bind被 C++ 11 标准接受,并在std::命名空间的<functional>头中定义。所有这些函数都不动态分配内存,也不使用虚函数。它们返回的对象对于一个好的编译器来说很容易优化。
这些函数的标准库实现可能有额外的优化来减少编译时间,或者只有编译器特定的优化。您可以将标准库版本的bind、ref、cref功能与任何 Boost 库一起使用,甚至可以混合使用 Boost 和标准库版本。
如果你使用的是 C++ 14 编译器,那么就用泛型 lambdas 来代替std::bind和boost::bind,因为它们不那么晦涩难懂,也更容易理解。与std::bind和boost::bind不同,C++ 17 lambdas 可以与 constexpr 一起使用。
官方文档包含了更多的例子和 http://boost.org/libs/bind.的高级特性描述
通常需要在运行时获得可读的类型名:
#include <iostream>
#include <typeinfo>
template <class T>
void do_something(const T& x) {
if (x == 0) {
std::cout << "Error: x == 0\. T is " << typeid(T).name()
<< std::endl;
}
// ...
}然而,前面的例子不是很容易移植。当 RTTI 被禁用时,它不起作用,并且它并不总是产生一个好的人类可读的名字。在一些平台上,早期的代码将只输出i或d。
如果我们需要一个类型名而不剥离const、volatile和引用,情况会变得更糟:
void sample1() {
auto&& x = 42;
std::cout << "x is "
<< typeid(decltype(x)).name()
<< std::endl;
}不幸的是,前面的代码在最好的情况下输出int,这不是我们所期望的。
这个食谱需要 C++ 的基础知识。
在第一种情况下,我们需要一个没有限定符的人类可读的类型名。Boost.TypeIndex图书馆将帮助我们:
#include <iostream>
#include <boost/type_index.hpp>
template <class T>
void do_something_again(const T& x) {
if (x == 0) {
std::cout << "x == 0\. T is " << boost::typeindex::type_id<T>()
<< std::endl;
}
// ...
}在第二种情况下,我们需要保留限定符,因此我们需要从同一个库中调用一个稍微不同的函数:
#include <boost/type_index.hpp>
void sample2() {
auto&& x = 42;
std::cout << "x is "
<< boost::typeindex::type_id_with_cvr<decltype(x)>()
<< std::endl;
}Boost.TypeIndex库对不同的编译器有很多变通方法,并且知道为类型产生一个人类可读名称的最有效的方法。如果您提供一个类型作为模板参数,库保证所有可能的类型相关计算将在编译时执行,即使 RTTI 被禁用,代码也将工作。
boost::typeindex::type_id_with_cvr中的cvr代表const、volatile,参考。这确保了该类型不会腐烂。
所有boost::typeindex::type_id*函数返回boost::typeindex::type_index的实例。它离std::type_index很近;但是,它另外还有一个获取原始类型名的raw_name()方法和一个获取人类可读类型名的pretty_name()。
即使在 C++ 17 中,std::type_index和std::type_info也返回平台特定的类型名称表示,这些表示很难解码或方便使用。
与标准库的typeid()不同,Boost.TypeIndex中的一些类可以与 constexpr 一起使用。这意味着如果你使用一个特定的boost::typeindex::ctti_type_index类,你可以在编译时得到你的类型的文本表示。
用户可以使用Boost.TypeIndex库发明他们自己的 RTTI 实现。这对于嵌入式开发人员和需要针对特定类型进行高效 RTTI 调优的应用可能非常有用。
关于高级特性和更多示例的文档可在http://boost.org/libs/type_index.获得
C++ 11 标准最大的特点之一是右值引用。这个特性允许我们修改临时对象,从它们那里窃取资源。正如您所猜测的,C++ 03 标准没有右值引用,但是使用Boost.Move库,您可以编写一个可移植的代码来模拟它们。
强烈建议您至少熟悉 C++ 11 右值引用的基础知识。
- 假设您有一个包含多个字段的类,其中一些字段是标准的库容器:
namespace other {
class characteristics{};
}
struct person_info {
std::string name_;
std::string second_name_;
other::characteristics characteristic_;
// ...
}; - 是时候添加移动赋值和移动构造函数了!请记住,在 C++ 03 标准库中,容器既没有移动运算符,也没有移动构造函数。
- 移动分配的正确实现是构造一个对象并将其与
this交换的相同移动。移动构造函数的正确实现接近默认构造和swap。那么,让我们从swap会员功能开始:
#include <boost/swap.hpp>
void person_info::swap(person_info& rhs) {
name_.swap(rhs.name_);
second_name_.swap(rhs.second_name_);
boost::swap(characteristic_, rhs.characteristic_);
} - 现在,将以下宏放入
private部分:
BOOST_COPYABLE_AND_MOVABLE(person_info) -
编写一个复制构造函数。
-
写一个拷贝赋值,取参数为:
BOOST_COPY_ASSIGN_REF(person_info)。 -
写一个
move构造函数和一个移动赋值,参数为BOOST_RV_REF(person_info):
struct person_info {
// Fields declared here
// ...
private:
BOOST_COPYABLE_AND_MOVABLE(person_info)
public:
// For the simplicity of example we will assume that
// person_info default constructor and swap are very
// fast/cheap to call.
person_info();
person_info(const person_info& p)
: name_(p.name_)
, second_name_(p.second_name_)
, characteristic_(p.characteristic_)
{}
person_info(BOOST_RV_REF(person_info) person) {
swap(person);
}
person_info& operator=(BOOST_COPY_ASSIGN_REF(person_info) person) {
person_info tmp(person);
swap(tmp);
return *this;
}
person_info& operator=(BOOST_RV_REF(person_info) person) {
person_info tmp(boost::move(person));
swap(tmp);
return *this;
}
void swap(person_info& rhs);
};- 现在,我们有了
person_info类的移动分配和移动构造操作符的便携式快速实现。
以下是如何使用移动分配的示例:
int main() {
person_info vasya;
vasya.name_ = "Vasya";
vasya.second_name_ = "Snow";
person_info new_vasya(boost::move(vasya));
assert(new_vasya.name_ == "Vasya");
assert(new_vasya.second_name_ == "Snow");
assert(vasya.name_.empty());
assert(vasya.second_name_.empty());
vasya = boost::move(new_vasya);
assert(vasya.name_ == "Vasya");
assert(vasya.second_name_ == "Snow");
assert(new_vasya.name_.empty());
assert(new_vasya.second_name_.empty());
}Boost.Move库以非常高效的方式实现。当使用 C++ 11 编译器时,所有用于右值仿真的宏都被扩展为 C++ 11 特定的特性,否则(在 C++ 03 编译器上),右值被仿真。
你注意到boost::swap呼叫了吗?这是一个非常有用的实用函数,它首先在变量的名称空间中搜索一个swap函数(在我们的例子中,它是名称空间other::,如果没有匹配的交换函数,它就使用std::swap。
- 更多关于仿真实现的信息可以在 Boost 网站和位于http://boost.org/libs/move.的
Boost.Move图书馆的资源中找到 Boost.Utility库是包含boost::swap的库,它有很多有用的函数和类。参考http://boost.org/libs/utility的文件。- 第二章、资源管理中通过派生配方的成员初始化基类。
- 制作不可复制类的食谱。
- 在制作不可复制但可移动的类配方中,有更多关于
Boost.Move的信息,以及一些关于我们如何以便携有效的方式使用容器中的可移动物体的例子。
您几乎肯定会遇到某些情况,其中一个类拥有一些资源,由于技术原因,这些资源不能被复制:
class descriptor_owner {
void* descriptor_;
public:
explicit descriptor_owner(const char* params);
~descriptor_owner() {
system_api_free_descriptor(descriptor_);
}
}; 前面例子中的 C++ 编译器生成了一个复制构造函数和一个赋值操作符,因此descriptor_owner类的潜在用户将能够创建以下可怕的东西:
void i_am_bad() {
descriptor_owner d1("O_o");
descriptor_owner d2("^_^");
// Descriptor of d2 was not correctly freed
d2 = d1;
// destructor of d2 will free the descriptor
// destructor of d1 will try to free already freed descriptor
}这个食谱只需要非常基本的 C++ 知识。
为了避免这种情况,发明了boost::noncopyable类。如果从中派生自己的类,C++ 编译器就不会生成复制构造函数和赋值运算符:
#include <boost/noncopyable.hpp>
class descriptor_owner_fixed : private boost::noncopyable {
// ... 现在,用户将无法做坏事:
void i_am_good() {
descriptor_owner_fixed d1("O_o");
descriptor_owner_fixed d2("^_^");
// Won't compile
d2 = d1;
// Won't compile either
descriptor_owner_fixed d3(d1);
}一个精致的读者会注意到,我们可以通过以下方式获得完全相同的结果:
- 将
descriptor_owning_fixed的复制构造函数和赋值运算符设为私有 - 在没有实际实现的情况下定义它们
- 使用 C++ 11 语法明确删除它们
= delete;
是的,你是正确的。根据编译器的能力,boost::noncopyable类选择使该类不可复制的最佳方式。
boost::noncopyable也是你上课的一个很好的文档。它从不提出诸如“复制构造函数体是否在其他地方定义了?”或者“它是否有非标准的复制构造函数(带有非常量引用的参数)?”
- 制作一个不可复制但可移动的类的方法会给你一些想法,告诉你如何在 C++ 03 中通过移动资源来唯一拥有它
- 你可以在 http://boost.org/libs/core 的
Boost.Core图书馆官方文档中找到很多有用的函数和类 - 第二章、资源管理中派生配方成员初始化基类
- 使用 C++ 11 的移动仿真配方
现在,想象一下下面的情况:我们有一个无法复制的资源,应该在析构函数中正确释放,我们希望从函数中返回它:
descriptor_owner construct_descriptor()
{
return descriptor_owner("Construct using this string");
} 实际上,您可以使用swap方法来解决这种情况:
void construct_descriptor1(descriptor_owner& ret)
{
descriptor_owner("Construct using this string").swap(ret);
} 但是,这样的变通方法不允许我们在容器中使用descriptor_owner。对了,看起来很可怕!
强烈建议您至少熟悉 C++ 11 右值引用的基础知识。还建议使用 C++ 11 移动仿真读取配方。
那些使用 C++ 11 的读者,已经知道了只动类(比如std::unique_ptr或者std::thread)。使用这样的方法,我们可以只动一动descriptor_owner类:
class descriptor_owner1 {
void* descriptor_;
public:
descriptor_owner1()
: descriptor_(nullptr)
{}
explicit descriptor_owner1(const char* param);
descriptor_owner1(descriptor_owner1&& param)
: descriptor_(param.descriptor_)
{
param.descriptor_ = nullptr;
}
descriptor_owner1& operator=(descriptor_owner1&& param) {
descriptor_owner1 tmp(std::move(param));
std::swap(descriptor_, tmp.descriptor_);
return *this;
}
void clear() {
free(descriptor_);
descriptor_ = nullptr;
}
bool empty() const {
return !descriptor_;
}
~descriptor_owner1() {
clear();
}
};
// GCC compiles the following in C++ 11 and later modes.
descriptor_owner1 construct_descriptor2() {
return descriptor_owner1("Construct using this string");
}
void foo_rv() {
std::cout << "C++ 11n";
descriptor_owner1 desc;
desc = construct_descriptor2();
assert(!desc.empty());
} 这仅适用于 C++ 11 兼容编译器。那是Boost.Move的正确时刻!让我们修改我们的示例,以便它可以在 C++ 03 编译器上使用。
根据文档,要用可移植语法编写可移动但不可复制的类型,我们需要遵循以下简单步骤:
- 将
BOOST_MOVABLE_BUT_NOT_COPYABLE(classname)宏放入private部分:
#include <boost/move/move.hpp>
class descriptor_owner_movable {
void* descriptor_;
BOOST_MOVABLE_BUT_NOT_COPYABLE(descriptor_owner_movable- 编写移动构造函数和移动赋值,参数为
BOOST_RV_REF(classname):
public:
descriptor_owner_movable()
: descriptor_(NULL)
{}
explicit descriptor_owner_movable(const char* param)
: descriptor_(strdup(param))
{}
descriptor_owner_movable(
BOOST_RV_REF(descriptor_owner_movable) param
) BOOST_NOEXCEPT
: descriptor_(param.descriptor_)
{
param.descriptor_ = NULL;
}
descriptor_owner_movable& operator=(
BOOST_RV_REF(descriptor_owner_movable) param) BOOST_NOEXCEPT
{
descriptor_owner_movable tmp(boost::move(param));
std::swap(descriptor_, tmp.descriptor_);
return *this;
}
// ...
};
descriptor_owner_movable construct_descriptor3() {
return descriptor_owner_movable("Construct using this string");
} 现在,我们有了一个可移动但不可复制的类,它甚至可以在 C++ 03 编译器和Boost.Containers中使用:
#include <boost/container/vector.hpp>
#include <your_project/descriptor_owner_movable.h>
int main() {
// Following code will work on C++ 11 and C++ 03 compilers
descriptor_owner_movable movable;
movable = construct_descriptor3();
boost::container::vector<descriptor_owner_movable> vec;
vec.resize(10);
vec.push_back(construct_descriptor3());
vec.back() = boost::move(vec.front());
}不幸的是,C++ 03 标准库容器仍然不能使用它(这就是为什么我们在前面的例子中使用了来自Boost.Containers的向量)。
如果你想在 C++ 03 编译器上使用Boost.Containers,但在 C++ 11 编译器上使用标准库容器,你可以做以下简单的技巧。将包含以下内容的头文件添加到项目中:
// your_project/vector.hpp
// Copyright and other stuff goes here
// include guards
#ifndef YOUR_PROJECT_VECTOR_HPP
#define YOUR_PROJECT_VECTOR_HPP
// Contains BOOST_NO_CXX11_RVALUE_REFERENCES macro.
#include <boost/config.hpp>
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
// We do have rvalues
#include <vector>
namespace your_project_namespace {
using std::vector;
} // your_project_namespace
#else
// We do NOT have rvalues
#include <boost/container/vector.hpp>
namespace your_project_namespace {
using boost::container::vector;
} // your_project_namespace
#endif // !defined(BOOST_NO_CXX11_RVALUE_REFERENCES)
#endif // YOUR_PROJECT_VECTOR_HPP 现在,您可以包含<your_project/vector.hpp>并使用来自命名空间your_project_namespace的向量:
int main() {
your_project_namespace::vector<descriptor_owner_movable> v;
v.resize(10);
v.push_back(construct_descriptor3());
v.back() = boost::move(v.front());
}但是,要小心编译器和标准库实现特定的问题!例如,仅当您用noexcept或BOOST_NOECEPT标记移动构造函数、析构函数和移动赋值运算符时,此代码才会在 C++ 11 模式下在 GCC 4.7 上编译。
- 第 10 章、收集平台和编译器信息中的在 C++ 11 配方中减少代码大小和提高用户定义类型的性能,提供了关于
noexcept和BOOST_NOEXCEPT的更多信息。 - 更多关于
Boost.Move的信息可以在 Boost 的网站http://boost.org/libs/move.上找到
C++ 11 在<algorithm>头有一堆新的很酷的算法。C++ 14 有更多的算法。如果你坚持 C++ 11 之前的编译器,你必须从头开始写。例如,如果您希望输出 65 到 125 个代码点的字符,您必须在 C++ 11 之前的编译器上编写以下代码:
#include <boost/array.hpp>
boost::array<unsigned char, 60> chars_65_125_pre11() {
boost::array<unsigned char, 60> res;
const unsigned char offset = 65;
for (std::size_t i = 0; i < res.size(); ++ i) {
res[i] = i + offset;
}
return res;
}这个食谱需要 C++ 的基础知识以及Boost.Array库的基础知识。
Boost.Algorithm库有所有新的 C++ 11 和 C++ 14 算法。使用它,您可以用以下方式重写前面的示例:
#include <boost/algorithm/cxx11/iota.hpp>
#include <boost/array.hpp>
boost::array<unsigned char, 60> chars_65_125() {
boost::array<unsigned char, 60> res;
boost::algorithm::iota(res.begin(), res.end(), 65);
return res;
}您可能已经知道,Boost.Algorithm每个算法都有一个头文件。只需包含头文件并使用所需的函数。
拥有一个只实现 C++ 标准算法的库是很无聊的。这不是创新;这不是助推方式!这就是为什么你可以在Boost.Algorithm中找到不属于 C++ 的函数。例如,这里有一个将输入转换为十六进制表示的函数:
#include <boost/algorithm/hex.hpp>
#include <iterator>
#include <iostream>
void to_hex_test1() {
const std::string data = "Hello word";
boost::algorithm::hex(
data.begin(), data.end(),
std::ostream_iterator<char>(std::cout)
);
}上述代码输出以下内容:
48656C6C6F20776F7264更有趣的是,所有的函数都有额外的重载,接受一个范围作为第一个参数,而不是两个迭代器。系列是来自系列 TS 的概念。具有.begin()和.end()功能的数组和容器满足范围概念。有了这些知识,前面的例子可以缩短:
#include <boost/algorithm/hex.hpp>
#include <iterator>
#include <iostream>
void to_hex_test2() {
const std::string data = "Hello word";
boost::algorithm::hex(
data,
std::ostream_iterator<char>(std::cout)
);
}C++ 17 将从Boost.Algorithm开始有搜索算法。Boost.Algorithm库将很快扩展新算法和 C++ 20 特性,如 constexpr 可用算法。关注这个库,因为有一天,它可能会为你正在处理的问题提供一个现成的解决方案。
Boost.Algorithm的官方文档包含了在http://boost.org/libs/algorithm的功能的完整列表和简短描述- 在线实验新算法:http://apolukhin.github.io/Boost-Cookbook