官术网_书友最值得收藏!

Simple data structures

In this section, we will look at two different libraries that will help you create simple data structures of immediate usefulness: Boost.Optional and Boost.Tuple. Boost.Optional can be used to represent optional values; objects that may or may not be there. Boost.Tuple is used to create ordered sets of heterogeneous values.

Boost.Optional

Let us consider that you need to maintain about musicians in a data store. Among other things, you can look up the latest album released by an artiste. You have written a simple API in C++ for doing this:

std::string find_latest_album_of(const std::string& artisteName);

For simplicity we will ignore the possibility that two or more artistes could share the same name. Here is a simple implementation of this function:

 1 #include <string>
 2 #include <map>
 3
 4 typedef std::map<std::string, std::string> artiste_album_map;
 5
 6 extern artiste_album_map latest_albums;
 7
 8 std::string find_latest_album_of(
 9                     const std::string& artiste_name) {
10 auto iter = latest_albums.find(artiste_name);
11
12   if (iter != latest_albums.end()) {
13     return iter->second;
14   } else {
15 return "";
16   }
17 }

We store the names of artistes and their latest albums in a map called latest_albums. The find_latest_album_of function takes the name of an artiste and uses the find member function of std::map to look up the latest album. If it does not find an entry, it returns an empty string. Now, it is possible that some artistes have not released an album yet. Returning an empty string seems legit for such cases until you realize that musicians have their unique whims and sometimes, they release an album without a name. So, how do you distinguish between the cases where the musician is yet to release an album, versus where the musician's latest album was untitled? In one case, there is no value to return while in the other case, it is an empty string.

The boost::optional<T> template can be used to represent an optional value; one that may or may not be present. In this case, it is tailor-made for our problem. To represent a std::string value that may or may not be present, you use boost::optional<std::string>. We can rewrite the find_latest_album_of function using boost::optional, as shown in the following code listing:

Listing 2.1: Using Boost.Optional

 1 #include <string>
 2 #include <map>
 3 #include <boost/optional.hpp>
 4
 5 typedef std::map<std::string, std::string> artiste_album_map;
 6
 7 extern artiste_album_map latest_albums;
 8 
 9 boost::optional<std::string> find_latest_album_of(
10                             const std::string& artiste_name) {
11   auto iter = latest_albums.find(artiste_name);
12
13   if (iter != latest_albums.end()) {
14 return iter->second;
15   } else {
16 return boost::none;
17   }
18 }

We simply return the value found (line 14), which is automatically wrapped in a boost::optional container. If there is no value to return, we return a special object, boost::none (line 16). This causes an empty boost::optional object to be returned. The code using boost::optional does exactly what we need; it checks whether a key is present in the container and returns the value or indicates that it is absent without any ambiguity (that is, empty versus untitled).

Tip

A default-initialized instance of boost::optional is always empty. If the value stored in boost::optional is movable (see Appendix, C++11 Language Features Emulation), the wrapper optional object is also movable. If the stored value is copyable, the wrapper optional object is also copyable.

We can generalize the lookup function in listing 2.1 to any container with a map-like or dictionary interface as follows:

Listing 2.2: Generic lookup using optional

 1 #include <boost/optional.hpp>
 2
 3 template <typename C>
 4 boost::optional<typename C::mapped_type>
 5 lookup(const C& dict, const typename C::key_type& key)
 6 {
 7   typename C::const_iterator it = dict.find(key);
 8   if (it != dict.end()) {
 9     return it->second;
10   } else {
11     return boost::none;
12   }
13 }

In the preceding code, we have converted lookup to a function template that can be called on any map, multimap, their unordered variants, or any other nonstandard container, exposing a similar interface. It is parameterized on the container type C. The container type C must have nested type definitions: key_type and mapped_type corresponding to the types of keys and values the map stores; a constraint satisfied by std:map and other associative containers from the Standard Library.

The use of the typename keyword (lines 4, 5, 7) may need some explanation. If we omit the typename keyword from these lines, the compiler will fail to identify C::mapped_type, C::key_type, and C::const_iterator as names of types. Because mapped_type, key_type, and const_iterator are names that are dependent on the type template parameter C, the compiler needs to be told that they identify types. We use the typename keyword to do this.

Accessing values stored in boost::optional

You can check whether an optional object contains a value or is empty, and extract the value stored in a non-empty optional object:

 1 std::string artiste("Korn");
 2 boost::optional<std::string> album = 
 3                             find_latest_album_of(artiste);
 4 if (album) {
 5   std::cout << "The last album from " << artiste;
 6
 7 if (album->empty()) {
 8     std::cout << " is untitled\n";
 9   } else {
10 std::cout << " is named " << *album << '\n';
11   }
12 } else {
13   std::cout << "No information on albums from " 
14             << artiste << '\n';
15 }

In the code that calls find_latest_album_of, to test whether the returned value is empty, we invoke the object in a Boolean context (line 4). If it evaluates to true, it means that album is not empty. If it has a value, we can obtain a reference to the contained value using the overloaded operator* (line 10). We can access members of the underlying object using an overloaded operator-> ; in this case we call the empty member function of std::string (line 7). We could also use get member function of a nonempty boost::optional object instead of the overloaded operator* to access the value stored. Dereferencing an empty optional value by calling the operator*, get, or operator-> causes a runtime error, which is why we first check whether the optional object is empty before trying to dereference it.

get_value_or

Using optional, we indicate that there may or may not be a value present for albums. But we would sometimes need to use APIs that should have taken optional values but do not. In such cases, we may want to return empty values with some default value. Imagine residents of Paris being asked about their favorite city and for those who do not name one, Paris being used as the default favorite:

 1 void printFavoriteCity(const std::string& name,
 2                        const std::string& city)
 3 {
 4   std::cout << name "'s favorite city is " << city << '\n';
 5 }
 6
 7 boost::optional<std::string> getFavoriteCity(
 8                           const std::string& resident_id);
 9 ...
10 std::string resident = "Serge";
11 boost::optional<std::string> fav_city = 
12                                     getFavoriteCity(resident);
13
14 printFavoriteCity(fav_city.get_value_or("Paris"));

If the imaginary getFavoriteCity function returns an empty value, we want Paris to be passed to the printFavoriteCity function. We do this using the get_value_or member function (line 14).

Boost.Optional versus pointers

If we did not use optional, what would the functions find_last_album_of or lookup return in order to indicate that there was no value found? They would either need to return a pointer to a dynamically-allocated object or nullptr if there was no value found. Besides using dynamic memory, it requires that the caller function manage the lifetime of the dynamically-allocated object that is returned. This condition can be mitigated using smart pointers (Chapter 3, Memory Management and Exception Safety), but it does not eliminate free store allocations that are costly. The boost::optional class eliminates free store allocations and stores the encapsulated object in its layout. In addition, it stores a Boolean flag to keep track of whether it is initialized or not.

Boost.Tuple

Boost Tuples are a cool way to group disparate types of data together into ordered tuples and pass them around. Structures do the same thing but a couple of things set tuples apart:

  • You can write generic code to manipulate tuples of all kinds, for example, to print all their members and comparing two tuples for similarity in structure and types.
  • Each new structure or class defines a new type in your software. Types should represent interfaces and behaviors. Representing every ad hoc clumping of data with a type results in proliferation of types that have no meaning in the problem space or its abstraction.

A Boost Tuple is an incredibly useful library that helps you conveniently create schemas for moving related data around together, such as exchanging data between functions. Boost Tuples are a generalization of std::pair, which is used to create 2-element tuples.

Tip

If you are using a C++ compiler with good C++11 support, you should use the std::tuple facility from the Standard Library—one of the Boost libraries that made it to the C++11 standard. The header to be included is <tuple>. Most of what we discuss here is applicable to std::tuple.

Creating tuples

Let us look at an example. Given a series of stock prices at different points in time, we want to find out the best two points in time to buy and sell the stock to maximize the profit. We can assume that there is no option to short-sell, that is, you must buy before you sell. For simplicity, the input can be assumed to be a vector of doubles. In this vector, we are interested in the pair of indices that represent the best time to buy and sell the stock to maximize profit:

Listing 2.3: Using tuples

 1 #include <boost/tuple/tuple.hpp>
 2 #include <vector>
 3
 4 boost::tuple<size_t, size_t, double>
 5      getBestTransactDays(std::vector<double> prices)
 6 {
 7   double min = std::numeric_limits<double>::max();
 8   double gain = 0.0, max_gain = 0.0;
 9   size_t min_day, max_day;
10   size_t buy_day;
11   for (size_t i = 0, days = prices.size(); i < days; ++i) {
12     if (prices[i] < min) {
13       min = prices[i];
14       min_day = i;
15     } else if ((gain = prices[i] - min) > max_gain) {
16       max_gain = gain;
17       buy_day = min_day;
18       max_day = i;
19     }
20   }
21
22 return boost::make_tuple(buy_day, max_day, max_gain);
23 }

The function getBestTransactDays returns a tuple of two unsigned integers (size_t) and a double (line 4) that represent the two indices at which buying and selling the stock would maximize profit, and the maximum profit possible. The return type of the function is boost::tuple<size_t, size_t, double>. The header boost/tuple/tuple.hpp provides the necessary functions and types for working with tuples (line 1).

The function getBestTransactDays implements a simple linear algorithm that runs through the vector, keeping track of the lowest stock price seen so far. If the current element has a lesser value than the lowest stock price so far, then this is set as the new lowest, and its index is noted (lines 12-14). The function also keeps track of the maximum gain, that is, the maximum difference in prices noted so far. If we encounter an element whose difference from the lowest price is higher than the maximum gain, then we note this difference as the new maximum gain (line 15), and also note the days of transaction required to achieve this gain (lines 16-18).

We create the tuple using boost::make_tuple (line 22), which is a convenience function for creating tuples from its elements without explicit template instantiations. You could have also created and returned a tuple like this in place of line 22:

22 boost::tuple<size_t, size_t, double> best_buy(buy_day, max_day, 
23                                         max_gain);
24 return best_buy;

As you can see, boost::make_tuple is more compact and, being a function template, resolves the types of its arguments automatically to create the tuple of correct types. This is a frequently seen pattern where you use a factory function template to instantiate a class template, thus automating type detection.

Accessing tuple elements

There are several ways in which we can access the elements in a tuple. Look at the following example of calling the getBestTransactDays function:

 1 std::vector<double> stockPrices;
 2 ...
 3 boost::tuple<size_t, size_t, double> best_buy = 
 4                              getBestTransactDays(stockPrices);
 5 
 6 size_t buyDay = boost::get<0>(best_buy);  // Access 0th element
 7 size_t sellDay = boost::get<1>(best_buy); // Access 1st element
 8 double profit = boost::get<2>(best_buy); // Access 2nd element

We can also unpack the elements in the tuple into individual variables using boost::tie:

 1 size_t buyDay, sellDay;
 2 double profit;
 3 boost::tie(buyDay, sellDay, profit) =  
 4                 getBestTransactDays(stockPrices);

The preceding line of code will assign the first element of the tuple to buyDay, the second to sellDay, and the third to profit. If we are interested in only a subset of the elements in the tuple, we can ignore the others using boost::tuples::ignore. Here is the same example, but we have ignored sellDay this time using boost::tuples::ignore:

 1 size_t buyDay, sellDay;
 2 boost::tie(buyDay, sellDay, boost::tuples::ignore) =
 3                              getBestTransactDays(stockPrices);

Comparing tuples

Tuples of the same length can be compared to relational operators, such as ==, <, >, <=, and >=. In any such comparison, the corresponding elements at each position are compared. The types of elements at the corresponding positions need not be identical; they just need to be comparable using the relational operator in question:

 1 boost::tuple<int, int, std::string> t1 = 
 2                          boost::make_tuple(1, 2, "Hello");
 3 boost::tuple<double, double, const char*> t2 = 
 4                         boost::make_tuple(1, 2, "Hi");
 5 assert(t1 < t2);   // because Hello < Hi

Note that the actual types in tuples t1 and t2 are different, but both have the same length, and the elements at corresponding positions are comparable with each other. In general, comparison stops at the first pair of elements that determines the outcome of the comparison. In this example, all three elements are compared because the first two elements compare equal.

 1 boost::tuple<int, int, std::string> t1 = 
 2                          boost::make_tuple(1, 20, "Hello");
 3 boost::tuple<double, double, const char*> t2 = 
 4                        boost::make_tuple(1, 2, "Hi");
 5 assert(t1 > t2);    // because 20 > 2

The following code is used to define relational operators for structs with very little code:

 1 struct my_type {
 2   int a;
 3   double b;
 4   char c;
 5 };
 6
 7 bool operator<(const my_type& left, const my_type& right) {
 8   return boost::make_tuple(left.a, left.b, left.c) <
 9                 boost::make_tuple(right.a, right.b, right.c);
10 }

Writing generic code using tuples

We will now write a generic function to find the number of elements in a tuple:

 1 template <typename T>
 2 size_t tuple_length(const T&) {
 3   return boost::tuples::length<T>::value;
 4 }

This function simply uses the boost::tuples::length<T> metafunction to compute the number of elements in the tuple. This computation takes place at compile time. A metafunction is just a class template that has an accessible static member or a nested type computed at compile time from its template arguments (see Chapter 7, Higher Order and Compile-time Programming, for a more rigorous definition). In this case, the boost::tuples::length<T> metafunction has a public static member called value, which is computed as the number of elements in the tuple T. If you use tuples from the Standard Library, you should use std::tuple_size<T> instead of boost::tuples::length<T>. This is just a small illustration of generic programming using metafunctions and type computation.

主站蜘蛛池模板: 安新县| 灵川县| 馆陶县| 项城市| 巩留县| 武宁县| 东山县| 襄垣县| 灵石县| 盐池县| 鄂尔多斯市| 巧家县| 德昌县| 勐海县| 咸阳市| 浦江县| 四子王旗| 灵丘县| 从化市| 平远县| 都江堰市| 德阳市| 和平区| 洛隆县| 大化| 克什克腾旗| 常宁市| 南澳县| 湟源县| 广灵县| 芜湖县| 德惠市| 察哈| 肥城市| 盘山县| 蒙自县| 扎鲁特旗| 西吉县| 运城市| 巴中市| 城市|