Você está na página 1de 5

Constructing graph algorithms with BGL

The main goal of BGL is not to provide a nice graph class, or to provide a comprehensive set of reusable graph algorithms (though these are goals). The main goal of BGL is to encourage others to write reusable graph algorithms. By reusable we mean maximally reusable. Generic programming is a methodology for making algorithms maximally reusable, and in this section we will discuss how to apply generic programming to constructing graph algorithms. To illustrate the generic programming process we will step though the construction of a graph coloring algorithm. The graph coloring problem (or more specifically, the vertex coloring problem) is to label each vertex in a graph G with a color such that no two adjacent vertices are labeled with the same color and such that the minimum number of colors are used. In general, the graph coloring problem is NP-complete, and therefore it is impossible to find an optimal solution in a reasonable amount of time. However, there are many algorithms that use heuristics to find colorings that are close to the minimum. The particular algorithm we present here is based on the linear time SEQ subroutine that is used in the estimation of sparse Jacobian and Hessian matrices [9,7,6]. This algorithm visits all of the vertices in the graph according to the order defined by the input order. At each vertex the algorithm marks the colors of the adjacent vertices, and then chooses the smallest unmarked color for the color of the current vertex. If all of the colors are already marked, a new color is created. A color is considered marked if its mark number is equal to the current vertex number. This saves the trouble of having to reset the marks for each vertex. The effectiveness of this algorithm is highly dependent on the input vertex order. There are several ordering algorithms, including the largest-first [31], smallest-last [29], and incidence degree [32] algorithms, which improve the effectiveness of this coloring algorithm. The first decision to make when constructing a generic graph algorithm is to decide what graph operations are necessary for implementing the algorithm, and which graph concepts the operations map to. In this algorithm we will need to traverse through all of the vertices to intialize the vertex colors. We also need to access the adjacent vertices. Therefore, we will choose the VertexListGraph concept because it is the minimum concept that includes these operations. The graph type will be parameterized in the template function for this algorithm. We do not restrict the graph type to a particular graph class, such as the BGL adjacency_list, for this would drastically limit the reusability of the algorithm (as most algorithms written to date are). We do restrict the graph type to those types that model VertexListGraph. This is enforced by the use of those graph operations in the algorithm, and furthermore by our explicit requirement added as a concept check with function_requires() (see Section Concept Checking for more details about concept checking). Next we need to think about what vertex or edge properties will be used in the algorithm. In this case, the only property is vertex color. The most flexible way to specify access to vertex color is to use the propery map interface. This gives the user of the algorithm the ability to decide how they want to store the properties. Since we will need to both read and write the colors we specify the requirements as ReadWritePropertyMap. The key_type of the color map must be the vertex_descriptor from the graph, and the value_type must be some kind of integer. We also specify the interface for the order parameter as a property map, in this case a ReadablePropertyMap. For order, the key_type is an integer offset and the value_type is a vertex_descriptor. Again we enforce these requirements with concept checks. The return value of this algorithm is the number of colors that were needed to color the graph, hence the return type of the function is the graph's vertices_size_type. The following code shows the interface for our graph algorithm as a template function, the concept checks, and some typedefs. The implementation is straightforward, the only step not discussed above is the color initialization step, where we set the color of all the vertices to ``uncolored''.
namespace boost {

template <class VertexListGraph, class Order, class Color> typename graph_traits<VertexListGraph>::vertices_size_type sequential_vertex_color_ting(const VertexListGraph& G, Order order, Color color) { typedef graph_traits<VertexListGraph> GraphTraits; typedef typename GraphTraits::vertex_descriptor vertex_descriptor; typedef typename GraphTraits::vertices_size_type size_type; typedef typename property_traits<Color>::value_type ColorType; typedef typename property_traits<Order>::value_type OrderType; function_requires< VertexListGraphConcept<VertexListGraph> >(); function_requires< ReadWritePropertyMapConcept<Color, vertex_descriptor> >(); function_requires< IntegerConcept<ColorType> >(); function_requires< size_type, ReadablePropertyMapConcept<Order> >(); typedef typename same_type<OrderType, vertex_descriptor>::type req_same; size_type max_color = 0; const size_type V = num_vertices(G); std::vector<size_type> mark(V, numeric_limits_max(max_color)); typename GraphTraits::vertex_iterator v, vend; for (tie(v, vend) = vertices(G); v != vend; ++v) color[*v] = V - 1; // which means "not colored" for (size_type i = 0; i < V; i++) { vertex_descriptor current = order[i]; // mark all the colors of the adjacent vertices typename GraphTraits::adjacency_iterator ai, aend; for (tie(ai, aend) = adjacent_vertices(current, G); ai != aend; ++ai) mark[color[*ai]] = i; // find the smallest color unused by the adjacent vertices size_type smallest_color = 0; while (smallest_color < max_color && mark[smallest_color] == i) ++smallest_color; // if all the colors are used up, increase the number of colors if (smallest_color == max_color) ++max_color; color[current] = smallest_color; } return max_color; } } // namespace boost

The Boost Concept Check Library (BCCL)


header boost/concept_check.hpp and boost/concept_archetype.hpp Generic programming in C++ is characterized by the use of template parameters to represent abstract data types (or ``concepts''). However, the C++ language itself does not provide a mechanism for the writer of a class or function template to explicitly state what concept the user-supplied template argument should model (or conform to). The common practice is to name the template parameter after the required concept as a hint to the user and to state the concept requirements in the documentation. However, often times the requirements are vague, incorrect, or nonexistent, which is quite a problem for the user, since he or she will not know exactly what kind of input is expected by the template. Furthermore, the following problems occur: Compiler error messages resulting from incorrect template arguments can be particularly difficult to decipher. Often times the error does not point to the location of the template call-site, but instead exposes the internals of the template, which the user should never have to see. The documented concept requirements may not fully cover the template, meaning the user could get a compiler error even though the supplied template arguments meet the documented requirements. The documented concept requirements may be too stringent, requiring more than is really needed by the template. The requirements are not explicitly stated in the code, which makes the code harder to understand. Also, the code may get out-of-sync with the documented requirements.

The Boost Concept Checking Library provides: A mechanism for inserting compile-time checks of template parameters. A framework for specifying concept requirements though concept checking classes. A mechanism for verifying that concept requirements cover the template. A suite of concept checking classes and archetype classes that match the concept requirements in the C++ Standard Library.

The mechanisms use standard C++ and introduce no run-time overhead. The main cost of using the mechanism is in compile-time. Any programmer writing class or function templates ought to make concept checking a normal part of their code writing routine. A concept check should be inserted for each template parameter in a component's public interface. If the concept is one of the ones from the Standard Library, then simply use the matching concept checking class in the BCCL. If not, then write a new concept checking class - after all, they are typically only a few lines long. For new concepts, a matching archetype class should also be created, which is a minimal skeleton-implementation of the concept The documentation is organized into the following sections. 1. Introduction 2. Motivating Example 3. History 4. Publications 5. Acknowledgements 6. Using Concept Checks 7. Creating Concept Checking Classes 8. Concept Covering and Archetypes 9. Implementation 10. Reference Jeremy Siek contributed this library. Beman Dawes managed the formal review.

Introduction A concept is a set of requirements (valid expressions, associated types, semantic invariants, complexity guarantees, etc.) that a type must fulfill to be correctly used as arguments in a call to a generic algorithm. In C++, concepts are represented by formal template parameters to function templates (generic algorithms). However, C++ has no explicit mechanism for representing concepts --- template parameters are merely placeholders. By convention, these parameters are given names corresponding to the concept that is required, but a C++ compiler does not enforce compliance to the concept when the template parameter is bound to an actual type. Naturally, if a generic algorithm is invoked with a type that does not fulfill at least the syntactic requirements of the concept, a compile-time error will occur. However, this error will not per se reflect the fact that the type did not meet all of the requirements of the concept. Rather, the error may occur deep inside the instantiation hierarchy at the point where an expression is not valid for the type, or where a presumed associated type is not available. The resulting error messages are largely uninformative and basically impenetrable. What is required is a mechanism for enforcing ``concept safety'' at (or close to) the point of instantiation. The Boost Concept Checking Library uses some standard C++ constructs to enforce early concept compliance and that provides more informative error messages upon non-compliance. Note that this technique only addresses the syntactic requirements of concepts (the valid expressions and associated types). We do not address the semantic invariants or complexity guarantees, which are also part of concept requirements.. Motivating Example We present a simple example to illustrate incorrect usage of a template library and the resulting error messages. In the code below, the generic std::stable_sort() algorithm from the Standard Template Library (STL)[3, 4,5] is applied to a linked list. bad_error_eg.cpp:
1 #include <list> 2 #include <algorithm> 3 4 int main(int, char*[]) { 5 std::list<int> v; 6 std::stable_sort(v.begin(), v.end()); 7 return 0; 8 }

Here, the std::stable_sort() algorithm is prototyped as follows:

template <class RandomAccessIterator> void stable_sort(RandomAccessIterator first, RandomAccessIterator last);

Attempting to compile this code with Gnu C++ produces the following compiler error. The output from other compilers is listed in the Appendix.
stl_algo.h: In function `void __merge_sort_loop<_List_iterator <int,int &,int *>, int *, int>(_List_iterator<int,int &,int *>, _List_iterator<int,int &,int *>, int *, int)': stl_algo.h:1448: instantiated from `__merge_sort_with_buffer <_List_iterator<int,int &,int *>, int *, int>( _List_iterator<int,int &,int *>, _List_iterator<int,int &,int *>, int *, int *)' stl_algo.h:1485: instantiated from `__stable_sort_adaptive< _List_iterator<int,int &,int *>, int *, int>(_List_iterator <int,int &,int *>, _List_iterator<int,int &,int *>, int *, int)' stl_algo.h:1524: instantiated from here stl_algo.h:1377: no match for `_List_iterator<int,int &,int *> & _List_iterator<int,int &,int *> &'

In this case, the fundamental error is that std:list::iterator does not model the concept of RandomAccessIterator. The list iterator is only bidirectional, not fully random access (as would be a vector iterator). Unfortunately, there is nothing in the error message to indicate this to the user.

To a C++ programmer having enough experience with template libraries the error may be obvious. However, for the uninitiated, there are several reasons why this message would be hard to understand. 1. The location of the error, line 6 of bad_error_eg.cpp is not pointed to by the error message, despite the fact that Gnu C++ prints up to 4 levels deep in the instantiation stack. 2. There is no textual correlation between the error message and the documented requirements for std::stable_sort() and for RandomAccessIterator. 3. The error message is overly long, listing functions internal to the STL that the user does not (and should not!) know or care about. 4. With so many internal library functions listed in the error message, the programmer could easily infer that the error is due to the library, rather than to his or her own code. The following is an example of what we might expect from a more informative message (and is in fact what the Boost Concept Checking Library produces):
boost/concept_check.hpp: In method `void LessThanComparableConcept <_List_iterator<int,int &,int *> >::constraints()': boost/concept_check.hpp:334: instantiated from `RandomAccessIteratorConcept <_List_iterator<int,int &,int *> >::constraints()' bad_error_eg.cpp:6: instantiated from `stable_sort<_List_iterator <int,int &,int *> >(_List_iterator<int,int &,int *>, _List_iterator<int,int &,int *>)' boost/concept_check.hpp:209: no match for `_List_iterator<int,int &,int *> & < _List_iterator<int,int &,int *> &'

This message rectifies several of the shortcomings of the standard error messages. The location of the error, bad_error_eg.cpp:6 is specified in the error message. The message refers explicitly to concepts that the user can look up in the STL documentation ( RandomAccessIterator). The error message is now much shorter and does not reveal internal STL functions. The presence of concept_check.hpp and constraints() in the error message alerts the user to the fact that the error lies in the user code and not in the library implementation.

History An earlier version of this concept checking system was developed by the author while working at SGI in their C++ compiler and library group. The earlier version is now part of the SGI STL distribution. The boost concept checking library differs from the concept checking in the SGI STL in that the definition of concept checking classes has been greatly simplified, at the price of less helpful verbiage in the error messages. Publications C++ Template Workshop 2000, Concept Checking

Acknowledgements The idea to use function pointers to cause instantiation is due to Alexander Stepanov. I am not sure of the origin of the idea to use expressions to do up-front checking of templates, but it did appear in D&E[ 2]. Thanks to Matt Austern for his excellent documentation and organization of the STL concepts, upon which these concept checks are based. Thanks to Boost members for helpful comments and reviews.

Você também pode gostar