CGAL 5.0.4 - STL Extensions for CGAL
|
CGAL is designed in the spirit of the generic programming paradigm to work together with the Standard Template Library (STL) [2], [1]. This chapter documents non-geometric STL-like components that are not provided in the STL standard but in CGAL: a doubly-connected list managing items in place (where inserted items are not copied), a compact container, a multi-set class that uses three-valued comparisons and offers additional functionality, generic algorithms, iterators, functor adaptors for binding and swapping arguments and for composition, functors for projection and creation and adaptor classes around iterators and circulators. See also circulators in Chapter Handles and Circulators. A class storing polymorphic objects is also provided, as well as a class to manage the uncertainty of some values. Finally, tags and policy classes to specify complexity trade-offs of data-structures, and a class which helps specifying that the default types in template parameter lists are desired is also provided.
The class In_place_list<T,bool>
manages a sequence of items in place in a doubly-connected list. Its goals are the flexible handling of memory management and performance optimization. The item type has to provide the two necessary pointers &T::next_link
and &T::prev_link
. One possibility to obtain these pointers is to inherit them from the base class In_place_list_base<T>
.
The class In_place_list<T,bool>
is a container quite similar to STL containers, with the advantage that it is able to handle the stored elements by reference instead of copying them. It is possible to delete an element only knowing its address and no iterator to it. This used to simplify mutually pointed data structures like a halfedge data structure for planar maps or polyhedral surfaces (the current design does not need this anymore). The usual iterators are also available.
The class Compact_container<T, Allocator>
is an STL like container which provides a very compact storage for its elements. It achieves this goal by requiring T
to provide access to a pointer in it, which is going to be used by Compact_container<T, Allocator>
for its internal management. The traits class Compact_container_traits<T>
specifies the way to access that pointer. The class Compact_container_base
can be used as a base class to provide the pointer, although in this case you do not get the most compact representation. The values that this pointer can have during valid use of the object are valid pointer values to 4 bytes aligned objects (i.e., the two least significant bits of the pointer need to be zero when the object is constructed). Another interesting property of this container is that iterators are not invalidated during insert
or erase
operations.
The main deviation from the STL container concept is that the ++
and --
operators of the iterator do not have a constant time complexity in all cases. The actual complexity is related to the maximum size that the container has had during its life time compared to its current size, because the iterator has to go over the "erased" elements as well, so the bad case is when the container used to contain lots of elements, but now has far less. In this case, we suggest to do a copy of the container in order to "defragment" the internal representation.
The objects stored in this container can optionally store an "erase counter". If it exists, i.e. if the object is a model of the ObjectWithEraseCounter
concept, each time an object is erased from the container, the erase counter of the object will be incremented. For example, this erase counter can be exploited using the CC_safe_handle
helper class, so that one can know if a handle is still pointing to the same element. Note that this is meaningful only because the container doesn't deallocate elements until the destruction or clear() of the container. For example, this counter is used by the parallel 3D mesh generation engine to lazily manage the queues of bad cells: an element in the queue is a pair containing a cell iterator and the erase counter value of the cell when it has been inserted. When an element is popped from the queue, the algorithm checks if the current value of the erase counter matches the stored value. If it doesn't match, it means the cell has been destroyed in the meantime and the algorithm ignores it. Without this lazy management, each time a cell is destroyed, the algorithm has to look for it in the queue and remove it. This mechanism is even more useful for the parallel version of the meshing process, since each thread has its own queue and looking for a cell in all the queues would be very slow.
This container has been developed in order to efficiently handle large data structures like the triangulation and halfedge data structures. It can probably be useful for other kinds of graphs as well.
The class Concurrent_compact_container<T, Allocator>
provides the same features, but enables concurrency-safe insert
and erase
operations. Other operations are not concurrency-safe. It requires the program to be linked against the Intel TBB library.
The class Multiset<Type,Compare,Allocator>
represents a multi-set of elements of type Type
, represented as a red-black tree (see [3] for an excellent introduction to red-black trees). It differs from the STL's multiset
class-template mainly due to the fact that it is parameterized by a comparison functor Compare
that returns the three-valued Comparison_result
(namely it returns either SMALLER
, EQUAL
, or LARGER
), rather than a less functor returning bool
. Thus, it is possible to maintain the underlying red-black tree with less invocations of the comparison functor, which can considerably decrease running times, especially when comparing elements of type Type
is an expensive operation.
Multiset<Type,Compare,Allocator>
also guarantees that the order of elements sent to the comparison functor is fixed. For example, if we insert a new element x
into the set (or erase an element from the set), then we always invoke Compare()(x, y)
(and never Compare()(y, x)
), where y
is an element already stored in the set. This behavior, not supported by std::multiset
, is sometimes crucial for designing more efficient comparison predicates.
The interface of Multiset<Type,Compare,Allocator>
is in general derived from std::multiset
. However, it extends the interface by offering some additional operations, such as: inserting of an element into the set given its exact position (and not just using an insertion hint); looking up keys whose type may differ from Type
, as long as users supply a comparison functor CompareKey
, between the keys and set elements; and catenating and splitting sets.
For handles and indices of vertices, halfedges, faces, etc., we provide specializations of boost::hash<T>
and std::hash<T>
, so that they can be used with classes such as boost::unordered_map
.
File STL_Extension/unordered_map.cpp
The class Object
can store an object of whatever other type. It can be used by a function to return objects of different types. A mechanism to extract the stored object based on its type is also provided. This class is similar to boost::any
.
The class Uncertain<T>
represents a range of values of type T
. T
is allowed to stand for bool
, or CGAL's enumeration types Sign
, Comparison_result
, Orientation
, Oriented_side
, Bounded_side
and Angle
.
The idea is that sometimes you are not sure of the result of a function, and you would like to communicate that to the caller. Uncertain<T>
allows just that. It also provides functions to naturally extend the Boolean operations for Uncertain<bool>
for example.
Uncertain<T>
is used in CGAL as the return type of geometric predicates when the number type used is interval arithmetic like Interval_nt
. End users typically do not see it as it is hidden in the implementation of the filtered predicates provided by the various filtered kernels, but it is important that providers of predicates that are meant to be filtered by Filtered_predicate
, know about it.
It can also be used in other contexts as well, as it is a general tool.
Some data structures and algorithms can be implemented with different complexity trade-offs between memory usage and time complexity. CGAL provides the tags Fast
and Compact
which can be used to select between those variants. For example, the Location_policy
class is parameterized by these tags and allows to specify the complexity of point location (currently in Delaunay_triangulation_3
only). Convenient typedefs Fast_location
and Compact_location
are also provided.
In C++, it is possible to specify defaults at the end of a template parameter list. Specifying that one wishes to use the default is simply done by omitting it. This is however possible only at the end of the list. Default
provides a simple mechanism that performs something equivalent anywhere in the sequence.
Much of the CGAL code contains checks. For example, all checks used in the kernel code are prefixed by CGAL_KERNEL
. Other packages have their own prefixes, as documented in the corresponding chapters. Some are there to check if the kernel behaves correctly, others are there to check if the user calls kernel routines in an acceptable manner.
There are five types of checks. The first three are errors and lead to a halt of the program if they fail. The fourth only leads to a warning, and the last one is compile-time only.
By default, all of these checks are performed. It is however possible to turn them off through the use of compile time switches. For example, for the checks in the kernel code, these switches are the following: CGAL_KERNEL_NO_PRECONDITIONS
, CGAL_KERNEL_NO_POSTCONDITIONS
, CGAL_KERNEL_NO_ASSERTIONS
and CGAL_KERNEL_NO_WARNINGS
.
So, in order to compile the file foo.cpp
with the postcondition checks off, you can do:
CC -DCGAL_KERNEL_NO_POSTCONDITIONS foo.cpp
This is also preferably done by modifying your makefile by adding -DCGAL_KERNEL_NO_POSTCONDITIONS
to the CXXFLAGS
variable.
The name KERNEL
in the macro name can be replaced by a package specific name in order to control assertions done in a given package. This name is given in the documentation of the corresponding package, in case it exists.
Note that global macros can also be used to control the behavior over the whole CGAL library:
CGAL_NO_PRECONDITIONS
,CGAL_NO_POSTCONDITIONS
,CGAL_NO_ASSERTIONS
,CGAL_NO_WARNINGS
andCGAL_NDEBUG
.Setting the macro CGAL_NDEBUG
disables all checks. This way, adding -DCGAL_NDEBUG
to your compilation flags removes absolutely all checks. This is the default recommended setup for performing timing benchmarks for example.
Note that the setting of the standard macro NDEBUG
sets CGAL_NDEBUG
, unless CGAL_DEBUG
is also defined. If both NDEBUG
and CGAL_DEBUG
are defined, then the the standard assert
macro is disabled, but not the CGAL assertions and preconditions.
Not all checks are on by default. The first four types of checks can be marked as expensive or exactness checks (or both). These checks need to be turned on explicitly by supplying one or both of the compile time switches CGAL_KERNEL_CHECK_EXPENSIVE
and CGAL_KERNEL_CHECK_EXACTNESS
.
Expensive checks are, as the word says, checks that take a considerable time to compute. Considerable is an imprecise phrase. Checks that add less than 10 percent to the execution time of the routine they are in are not expensive. Checks that can double the execution time are. Somewhere in between lies the border line. Checks that increase the asymptotic running time of an algorithm are always considered expensive. Exactness checks are checks that rely on exact arithmetic. For example, if the intersection of two lines is computed, the postcondition of this routine may state that the intersection point lies on both lines. However, if the computation is done with doubles as number type, this may not be the case, due to round off errors. So, exactness checks should only be turned on if the computation is done with some exact number type.
By definition, static assertions are both inexpensive and unaffected by precision management. Thus, the categories do not apply for static assertions.
As stated above, if a postcondition, precondition or assertion is violated, an exception is thrown, and if nothing is done to catch it, the program will abort. This behavior can be changed by means of the function set_error_behaviour()
and the enum Failure_behaviour
.
The THROW_EXCEPTION
value is the default, which throws an exception.
If the EXIT
value is set, the program will stop and return a value indicating failure, but not dump the core. The CONTINUE
value tells the checks to go on after diagnosing the error. Note that since CGAL 3.4, CONTINUE
has the same effect as THROW_EXCEPTION
for errors (but it keeps its meaning for warnings), it is not possible anymore to let assertion failures simply continue (except by totally disabling them).
If the EXIT_WITH_SUCCESS
value is set, the program will stop and return a value corresponding to successful execution and not dump the core.
The value that is returned by set_error_behaviour()
is the value that was in use before.
For warnings we provide set_warning_behaviour()
which works in the same way. The only difference is that for warnings the default value is CONTINUE
.
Setting the error and warning behavior is not thead safe.
The compile time flags as described up to now all operate on the whole library. Sometimes you may want to have a finer control. CGAL offers the possibility to turn checks on and off with a bit finer granularity, namely the module in which the routines are defined. The name of the module is to be appended directly after the CGAL prefix. So, the flag CGAL_KERNEL_NO_ASSERTIONS
switches off assertions in the kernel only, the flag CGAL_CH_CHECK_EXPENSIVE
turns on expensive checks in the convex hull module. The name of a particular module is documented with that module.
Normally, error messages are written to the standard error output. It is possible to do something different with them. To that end you can register your own handler using set_error_handler(Failure_function handler)
This function should be declared as follows.
There are several things that you can do with your own handler. You can display a diagnostic message in a different way, for instance in a pop up window or to a log file (or a combination). You can also implement a different policy on what to do after an error. For instance, you can throw an exception or ask the user in a dialog whether to abort or to continue. If you do this, it is best to set the error behavior to CONTINUE
, so that it does not interfere with your policy.
You can register two handlers, one for warnings and one for errors. Of course, you can use the same function for both if you want. When you set a handler, the previous handler is returned, so you can restore it if you want.