14.2. 元组

Boost.Tuple 库提供了一个更一般的版本的 std::pair —— boost::tuple 。 不过 std::pair 只能储存两个值而已, boost::tuple 则给了我们更多的选择。

  1. #include <boost/tuple/tuple.hpp>
  2. #include <boost/tuple/tuple_io.hpp>
  3. #include <string>
  4. #include <iostream>
  5.  
  6. int main()
  7. {
  8. typedef boost::tuple<std::string, std::string> person;
  9. person p("Boris", "Schaeling");
  10. std::cout << p << std::endl;
  11. }

为了使用 boost::tuple, 你必须要包含头文件: boost/tuple/tuple.hpp 。 若想要让元组和流一起使用, 你还需要包含头文件: boost/tuple/tuple_io.hpp 才行。

其实, boost::tuple 的用法基本上和 std::pair 一样。 就像我们在上面的例子里看到的那样, 两个值类型的 std::string 通过两个相应的模板参数存储在了元组里。

当然 person 类型也可以用 std::pair 来实现。 所有 boost::tuple 类型的对象都可以被写入流里。 再次强调, 为了使用流操作和各种流操作运算符, 你必须要包含头文件: boost/tuple/tuple_io.hpp 。 显然,我们的例子会输出: (Boris Schaeling)

boost::tuplestd::pair 之间最重要的一点不同点: 元组可以存储无限多个值!

  1. #include <boost/tuple/tuple.hpp>
  2. #include <boost/tuple/tuple_io.hpp>
  3. #include <string>
  4. #include <iostream>
  5.  
  6. int main()
  7. {
  8. typedef boost::tuple<std::string, std::string, int> person;
  9. person p("Boris", "Schaeling", 43);
  10. std::cout << p << std::endl;
  11. }

我们修改了实例, 现在的元组里不仅储存了一个人的firstname和lastname, 还加上了他的鞋子的尺码。 现在, 我们的例子将会输出: (Boris Schaeling 43)

就像 std::pair 有辅助函数 std::make_pair() 一样, 一个元组也可以用它的辅助函数 boost::make_tuple() 来创建。

  1. #include <boost/tuple/tuple.hpp>
  2. #include <boost/tuple/tuple_io.hpp>
  3. #include <iostream>
  4.  
  5. int main()
  6. {
  7. std::cout << boost::make_tuple("Boris", "Schaeling", 43) << std::endl;
  8. }

就像下面的例子所演示的那样, 一个元组也可以存储引用类型的值。

  1. #include <boost/tuple/tuple.hpp>
  2. #include <boost/tuple/tuple_io.hpp>
  3. #include <string>
  4. #include <iostream>
  5.  
  6. int main()
  7. {
  8. std::string s = "Boris";
  9. std::cout << boost::make_tuple(boost::ref(s), "Schaeling", 43) << std::endl;
  10. }

因为 "Schaeling" 和 43 是按值传递的,所以就直接存储在了元组中。 与他们不同的是: person 的第一个元素是一个指向 s 的引用。 Boost.Ref 中的 boost::ref() 就是用来创建这样的引用的。 相对的, 要创建一个常量的引用的时候, 你需要使用 boost::cref()

在学习了创建元组的方法之后, 让我们来了解一下访问元组中元素的方式。 std::pair 只包含两个元素, 故可以使用属性 firstsecond 来访问其中的元素。 但元组可以包含无限多个元素, 显然, 我们需要用另一种方式来解决访问的问题。

  1. #include <boost/tuple/tuple.hpp>
  2. #include <string>
  3. #include <iostream>
  4.  
  5. int main()
  6. {
  7. typedef boost::tuple<std::string, std::string, int> person;
  8. person p = boost::make_tuple("Boris", "Schaeling", 43);
  9. std::cout << p.get<0>() << std::endl;
  10. std::cout << boost::get<0>(p) << std::endl;
  11. }

我们可以用两种方式来访问元组中的元素: 使用成员函数 get() , 或者将元组传给一个独立的函数 boost::get() 。 使用这两种方式时, 元素的索引值都是通过模板参数来指定的。 例子中就分别使用了这两种方式来访问 p 中的第一个元素。 因此, Boris 会被输出两次。

另外, 对于索引值合法性的检查会在编译期执行, 故访问非法的索引值会引起编译期错误而不是运行时的错误。

对于元组中元素的修改, 你同样可以使用 get()boost::get() 函数。

  1. #include <boost/tuple/tuple.hpp>
  2. #include <boost/tuple/tuple_io.hpp>
  3. #include <string>
  4. #include <iostream>
  5.  
  6. int main()
  7. {
  8. typedef boost::tuple<std::string, std::string, int> person;
  9. person p = boost::make_tuple("Boris", "Schaeling", 43);
  10. p.get<1>() = "Becker";
  11. std::cout << p << std::endl;
  12. }

get()boost::get() 都会返回一个引用值。 例子中修改了 lastname 之后将会输出: (Boris Becker 43)

Boost.Tuple 除了重载了流操作运算符以外, 还为我们提供了比较运算符。 为了使用它们, 你必须要包含相应的头文件: boost/tuple/tuple_comparison.hpp

  1. #include <boost/tuple/tuple.hpp>
  2. #include <boost/tuple/tuple_comparison.hpp>
  3. #include <string>
  4. #include <iostream>
  5.  
  6. int main()
  7. {
  8. typedef boost::tuple<std::string, std::string, int> person;
  9. person p1 = boost::make_tuple("Boris", "Schaeling", 43);
  10. person p2 = boost::make_tuple("Boris", "Becker", 43);
  11. std::cout << (p1 != p2) << std::endl;
  12. }

上面的例子将会输出 1 因为两个元组 p1p2 是不同的。

同时, 头文件 boost/tuple/tuple_comparison.hpp 还定义了一些其他的比较操作, 比如用来做字典序比较的大于操作等。

Boost.Tuple 还提供了一种叫做 Tier 的特殊元组。 Tier 的特殊之处在于它包含的所有元素都是引用类型的。 它可以通过构造函数 boost::tie() 来创建。

  1. #include <boost/tuple/tuple.hpp>
  2. #include <boost/tuple/tuple_io.hpp>
  3. #include <string>
  4. #include <iostream>
  5.  
  6. int main()
  7. {
  8. typedef boost::tuple<std::string&, std::string&, int&> person;
  9.  
  10. std::string firstname = "Boris";
  11. std::string surname = "Schaeling";
  12. int shoesize = 43;
  13. person p = boost::tie(firstname, surname, shoesize);
  14. surname = "Becker";
  15. std::cout << p << std::endl;
  16. }

上面的例子创建了一个 tier p, 他包含了三个分别指向 firstnamesurnameshoesize 的引用值。 在修改变量 surname 的同时, tier 也会跟着改变。

就像下面的例子展示的那样,你当然可以用 boost::make_tuple()boost::ref() 来代替构造函数 boost::tie()

  1. #include <boost/tuple/tuple.hpp>
  2. #include <boost/tuple/tuple_io.hpp>
  3. #include <string>
  4. #include <iostream>
  5.  
  6. int main()
  7. {
  8. typedef boost::tuple<std::string&, std::string&, int&> person;
  9.  
  10. std::string firstname = "Boris";
  11. std::string surname = "Schaeling";
  12. int shoesize = 43;
  13. person p = boost::make_tuple(boost::ref(firstname), boost::ref(surname), boost::ref(shoesize));
  14. surname = "Becker";
  15. std::cout << p << std::endl;
  16. }

boost::tie() 在一定程度上简化了语法, 同时, 也可以用作“拆箱”元组。 在接下来的这个例子里, 元组中的各个元素就被很方便的“拆箱”并直接赋给了其他变量。

  1. #include <boost/tuple/tuple.hpp>
  2. #include <string>
  3. #include <iostream>
  4.  
  5. boost::tuple<std::string, int> func()
  6. {
  7. return boost::make_tuple("Error message", 2009);
  8. }
  9.  
  10. int main()
  11. {
  12. std::string errmsg;
  13. int errcode;
  14.  
  15. boost::tie(errmsg, errcode) = func();
  16. std::cout << errmsg << ": " << errcode << std::endl;
  17. }

通过使用 boost::tie() , 元组中的元素:字符串“Error massage”和错误代码“2009”就很方便地经 func() 的返回值直接赋给了 errmsgerrcode