diff --git a/count_bdy.htm b/count_bdy.htm deleted file mode 100644 index 67e9087..0000000 --- a/count_bdy.htm +++ /dev/null @@ -1,774 +0,0 @@ - - - -
- - - - - - -Reference counting techniques? Nothing new, you might think. Every good - C++ text that takes you to an intermediate or advanced level will - introduce the concept. It has been explored with such thoroughness in the - past that you might be forgiven for thinking that everything that can be - said has been said. Well, let's start from first principles and see if we - can unearth something new....
-The principle behind reference counting is to keep a running usage - count of an object so that when it falls to zero we know the object is - unused. This is normally used to simplify the memory management for - dynamically allocated objects: keep a count of the number of references - held to that object and, on zero, delete the object.
- -How to keep a track of the number of users of an object? Well, normal - pointers are quite dumb, and so an extra level of indirection is required - to manage the count. This is essentially the PROXY - pattern described in Design Patterns [Gamma, Helm, Johnson & - Vlissides, Addison-Wesley, ISBN 0-201-63361-2]. The - intent is given as
- -Provide a surrogate or placeholder for another object to control - access to it.
-Coplien [Advanced C++ Programming Styles and Idioms, - Addison-Wesley, ISBN 0-201-56365-7] defines a set - of idioms related to this essential separation of a handle and a body - part. The Taligent Guide to Designing Programs [Addison-Wesley, - ISBN 0-201-40888-0] identifies a number of specific - categories for proxies (aka surrogates). Broadly speaking they fall into - two general categories:
- -For reference counted smart pointers there are two places the count can - exist, resulting in two different patterns, both outlined in - Software Patterns [Coplien, SIGS, ISBN - 0-884842-50-X]:
- -Even with this simple analysis, it seems that the DETACHED COUNTED HANDLE/BODY approach is ahead. Indeed, - with the increasing use of templates this is often the favourite, and is - the principle behind the common - but not standard - counted_ptr. [The Boost name is shared_ptr rather than counted_ptr.]
- -A common implementation of COUNTED BODY is to provide the counting mechanism in a base class that - the counted type is derived from. Either that, or the reference counting - mechanism is provided anew for each class that needs it. Both of these - approaches are unsatisfactory because they are quite closed, coupling a - class into a particular framework. Added to this the non-cohesiveness of - having the count lying dormant in a non-counted object, and you get the - feeling that excepting its use in widespread object models such as COM and - CORBA the COUNTED BODY - approach is perhaps only of use in specialised situations.
-It is the question of openness that convinced me to revisit the - problems with the COUNTED BODY idiom. Yes, there is a certain degree of intrusion - expected when using this idiom, but is there anyway to minimise this and - decouple the choice of counting mechanism from the smart pointer type - used?
- -In recent years the most instructive body of code and specification for - constructing open general purpose components has been the Stepanov and - Lee's STL (Standard Template Library), now part of the C++ standard - library. The STL approach makes extensive use of compile time polymorphism - based on well defined operational requirements for types. For instance, - each container, contained and iterator type is defined by the operations - that should be performable on an object of that type, often with - annotations describing additional constraints. Compile time polymorphism, - as its name suggests, resolves functions at compile time based on function - name and argument usage, i.e. overloading. This is less intrusive, - although less easily diagnosed if incorrect, than runtime poymorphism that - is based on types, names and function signatures.
- -This requirements based approach can be applied to reference counting. - The operations we need for a type to be Countable are loosely:
- -Note that the count is deduced as a part of the abstract state of this - type, and is not mentioned or defined in any other way. The openness of - this approach derives in part from the use of global functions, meaning - that no particular member functions are implied; a perfect way to wrap up - an existing counted body class without modifying the class itself. The - other aspect to the openness comes from a more precise specification of - the operations.
- -For a type to be Countable it must satisfy the following - requirements, where ptr is a non-null - pointer to a single object (i.e. not an array) of the type, and - #function indicates number of calls - to function(ptr):
- -Expression | - -Return type | - -Semantics and notes | -
acquire(ptr) | - -no requirement | - -post: acquired(ptr) | -
release(ptr) | - -no requirement | - -pre: acquired(ptr) - post: acquired(ptr) == #acquire - - #release |
-
acquired(ptr) | - -convertible to bool | - -return: #acquire > #release | -
dispose(ptr, ptr) | - -no requirement | - -pre: !acquired(ptr) - post: *ptr no longer usable |
-
Note that the two arguments to dispose - are to support selection of the appropriate type safe version of the - function to be called. In the general case the intent is that the first - argument determines the type to be deleted, and would typically be - templated, while the second selects which template to use, e.g. by - conforming to a specific base class.
- -In addition the following requirements must also be satisfied, where - null is a null pointer to the - Countable type:
- -Expression | - -Return type | - -Semantics and notes | -
acquire(null) | - -no requirement | - -action: none | -
release(null) | - -no requirement | - -action: none | -
acquired(null) | - -convertible to bool | - -return: false | -
dispose(null, null) | - -no requirement | - -action: none | -
Note that there are no requirements on these functions in terms of - exceptions thrown or not thrown, except that if exceptions are thrown the - functions themselves should be exception safe.
-Given the Countable requirements for a type, it is possible to - define a generic smart pointer type that uses them for reference counting:
- --template<typename countable_type> -class countable_ptr -{ -public: // construction and destruction - - explicit countable_ptr(countable_type *); - countable_ptr(const countable_ptr &); - ~countable_ptr(); - -public: // access - - countable_type *operator->() const; - countable_type &operator*() const; - countable_type *get() const; - -public: // modification - - countable_ptr &clear(); - countable_ptr &assign(countable_type *); - countable_ptr &assign(const countable_ptr &); - countable_ptr &operator=(const countable_ptr &); - -private: // representation - - countable_type *body; - -}; - --
The interface to this class has been kept intentionally simple, e.g. - member templates and throw specs have been - omitted, for exposition. The majority of the functions are quite simple in - implementation, relying very much on the assign member as a keystone function:
- --template<typename countable_type> -countable_ptr<countable_type>::countable_ptr(countable_type *initial) - : body(initial) -{ - acquire(body); -} - -template<typename countable_type> -countable_ptr<countable_type>::countable_ptr(const countable_ptr &other) - : body(other.body) -{ - acquire(body); -} - -template<typename countable_type> -countable_ptr<countable_type>::~countable_ptr() -{ - clear(); -} - -template<typename countable_type> -countable_type *countable_ptr<countable_type>::operator->() const -{ - return body; -} - -template<typename countable_type> -countable_type &countable_ptr<countable_type>::operator*() const -{ - return *body; -} - -template<typename countable_type> -countable_type *countable_ptr<countable_type>::get() const -{ - return body; -} - -template<typename countable_type> -countable_ptr<countable_type> &countable_ptr<countable_type>::clear() -{ - return assign(0); -} - -template<typename countable_type> -countable_ptr<countable_type> &countable_ptr<countable_type>::assign(countable_type *rhs) -{ - // set to rhs (uses Copy Before Release idiom which is self assignment safe) - acquire(rhs); - countable_type *old_body = body; - body = rhs; - - // tidy up - release(old_body); - if(!acquired(old_body)) - { - dispose(old_body, old_body); - } - - return *this; -} - -template<typename countable_type> -countable_ptr<countable_type> &countable_ptr<countable_type>::assign(const countable_ptr &rhs) -{ - return assign(rhs.body); -} - -template<typename countable_type> -countable_ptr<countable_type> &countable_ptr<countable_type>::operator=(const countable_ptr &rhs) -{ - return assign(rhs); -} - --
Conformance to the requirements means that a type can be used with - countable_ptr. Here is an implementation - mix-in class (mix-imp) that confers countability on its derived - classes through member functions. This class can be used as a class - adaptor:
- --class countability -{ -public: // manipulation - - void acquire() const; - void release() const; - size_t acquired() const; - -protected: // construction and destruction - - countability(); - ~countability(); - -private: // representation - - mutable size_t count; - -private: // prevention - - countability(const countability &); - countability &operator=(const countability &); - -}; - --
Notice that the manipulation functions are const and that the count - member itself is mutable. This is because - countability is not a part of an object's abstract state: memory - management does not depend on the const-ness or otherwise of an object. I won't include the - definitions of the member functions here as you can probably guess them: - increment, decrement and return the current count, respectively for the - manipulation functions. In a multithreaded environment you should ensure - that such read and write operations are atomic.
- -So how do we make this class Countable? A simple set of - forwarding functions does the job:
- --void acquire(const countability *ptr) -{ - if(ptr) - { - ptr->acquire(); - } -} - -void release(const countability *ptr) -{ - if(ptr) - { - ptr->release(); - } -} - -size_t acquired(const countability *ptr) -{ - return ptr ? ptr->acquired() : 0; -} - -template<class countability_derived> -void dispose(const countability_derived *ptr, const countability *) -{ - delete ptr; -} - --
Any type that now derives from countability may now be used with countable_ptr:
- --class example : public countability -{ - ... -}; - -void simple() -{ - countable_ptr<example> ptr(new example); - countable_ptr<example> qtr(ptr); - ptr.clear(); // set ptr to point to null -} // allocated object deleted when qtr destructs - --
The challenge is to apply COUNTED BODY in a non-intrusive fashion, such that there is no overhead - when an object is not counted. What we would like to do is confer this - capability on a per object rather than on a per class basis. Effectively - we are after Countability on any object, i.e. anything pointed to - by a void *! It goes without saying that - void is perhaps the least committed of any type.
- -The forces to resolve on this are quite interesting, to say the least. - Interesting, but not insurmountable. Given that the class of a runtime - object cannot change dynamically in any well defined manner, and the - layout of the object must be fixed, we have to find a new place and time - to add the counting state. The fact that this must be added only on heap - creation suggests the following solution:
- --struct countable_new; -extern const countable_new countable; - -void *operator new(size_t, const countable_new &); -void operator delete(void *, const countable_new &); --
We have overloaded operator new with a - dummy argument to distinguish it from the regular global operator new. This is comparable to the use of the - std::nothrow_t type and std::nothrow object in the standard library. The - placement operator delete is there to - perform any tidy up in the event of failed construction. Note that this is - not yet supported on all that many compilers.
- -The result of a new expression using - countable is an object allocated on the - heap that has a header block that holds the count, i.e. we have extended - the object by prefixing it. We can provide a couple of features in an - anonymous namespace (not shown) in the implementation file for for - supporting the count and its access from a raw pointer:
- --struct count -{ - size_t value; -}; - -count *header(const void *ptr) -{ - return const_cast<count *>(static_cast<const count *>(ptr) - 1); -} - --
An important constraint to observe here is the alignment of - count should be such that it is suitably - aligned for any type. For the definition shown this will be the case on - almost all platforms. However, you may need to add a padding member for - those that don't, e.g. using an anonymous union to coalign count - and the most aligned type. Unfortunately, there is no portable way of - specifying this such that the minimum alignment is also observed - this is - a common problem when specifying your own allocators that do not directly - use the results of either new or - malloc.
- -Again, note that the count is not considered to be a part of the - logical state of the object, and hence the conversion from - const to non-const - count is in - effect a mutable type.
- -The allocator functions themselves are fairly straightforward:
- --void *operator new(size_t size, const countable_new &) -{ - count *allocated = static_cast<count *>(::operator new(sizeof(count) + size)); - *allocated = count(); // initialise the header - return allocated + 1; // adjust result to point to the body -} - -void operator delete(void *ptr, const countable_new &) -{ - ::operator delete(header(ptr)); -} - --
Given a correctly allocated header, we now need the Countable - functions to operate on const void * to - complete the picture:
- --void acquire(const void *ptr) -{ - if(ptr) - { - ++header(ptr)->value; - } -} - -void release(const void *ptr) -{ - if(ptr) - { - --header(ptr)->value; - } -} - -size_t acquired(const void *ptr) -{ - return ptr ? header(ptr)->value : 0; -} - -template<typename countable_type> -void dispose(const countable_type *ptr, const void *) -{ - ptr->~countable_type(); - operator delete(const_cast<countable_type *>(ptr), countable); -} - --
The most complex of these is the dispose function that must ensure that the correct type - is destructed and also that the memory is collected from the correct - offset. It uses the value and type of first argument to perform this - correctly, and the second argument merely acts as a strategy selector, - i.e. the use of const void * - distinguishes it from the earlier dispose shown for const countability *.
-Now that we have a way of adding countability at creation for objects - of any type, what extra is needed to make this work with the - countable_ptr we defined earlier? Good - news: nothing!
- --class example -{ - ... -}; - -void simple() -{ - countable_ptr<example> ptr(new(countable) example); - countable_ptr<example> qtr(ptr); - ptr.clear(); // set ptr to point to null -} // allocated object deleted when qtr destructs - --
The new(countable) expression defines a - different policy for allocation and deallocation and, in common with other - allocators, any attempt to mix your allocation policies, e.g. call - delete on an object allocated with - new(countable), results in undefined - behaviour. This is similar to what happens when you mix new[] with delete or - malloc with delete. The whole point of Countable conformance - is that Countable objects are used with countable_ptr, and this ensures the correct use.
- -However, accidents will happen, and inevitably you may forget to - allocate using new(countable) and instead - use new. This error and others can be - detected in most cases by extending the code shown here to add a check - member to the count, validating the check - on every access. A benefit of ensuring clear separation between header and - implementation source files means that you can introduce a checking - version of this allocator without having to recompile your code.
-There are two key concepts that this article has introduced:
- -The application of the two together gives rise to a new variant of the - essential COUNTED BODY - pattern, UNINTRUSIVE COUNTED BODY. You can take this theme - even further and contrive a simple garbage collection system for C++.
- -The complete code for countable_ptr, - countability, and the countable new is also available.
-Revised - 04 December, 2006
- -Copyright © 1998-1999 Kevlin Henney
- -Distributed under the Boost Software License, Version 1.0. (See - accompanying file LICENSE_1_0.txt or copy - at http://www.boost.org/LICENSE_1_0.txt)
- - diff --git a/cpp_committee_meetings.html b/cpp_committee_meetings.html deleted file mode 100644 index 26bc51a..0000000 --- a/cpp_committee_meetings.html +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - -Who can attend C++ Committee meetings? Members of -J16 (the INCITS/ANSI committee) or of a WG21 (ISO) member country committee -("national body" in -ISO-speak). -INCITS has broadened J16 membership requirements so anyone can -join, regardless of nationality or employer.
-In addition, a small number of "technical experts" who are not committee -members can also attend meetings. The "technical expert" umbrella is broad enough to cover -the -Boost members who attend meetings.
-When and where is the next meeting? There are two meetings a year. The -Fall meeting is usually in North America, and the Spring meeting is usually -outside North America. See a general -list of meeting locations and -dates. Detailed information about a particular meeting, including hotel -information, is usually provided in a paper appearing in one of -mailings for the prior meeting. If there isn't a link to -it on the -Meetings web page, you will have to go to -the committee's -Papers page and search a bit.
-Is there a fee for attending meetings? No, but there can be a lot of -incidental expenses like travel, lodging, and meals, and there is a $US 800 a -year INCITS fee to become a voting member.
-What is the schedule? The meetings start at 9:00AM on -Monday, and 8:30AM other days, unless otherwise announced. It is best to arrive -a half-hour early to grab a good seat, some coffee, tea, or donuts, and to say -hello to people. (There is also a Sunday evening a WG21 administrative meeting, -which is closed except to delegates from national bodies.)
-The meetings generally end on Friday, although there is discussion of -extending them one extra day until the next standard ships. The last day the meeting is generally over by 11:00AM. Because -the last day's meeting is for formal votes only, it is primarily of interest only to -actual committee -members.
-Sometimes there are evening technical sessions; the details aren't -usually available until the Monday morning meeting. There may be a -reception one evening, and, yes, significant others are -invited. Again, details usually become available Monday morning.
-What actually happens at the meetings? Monday morning an hour or two -is spent in full committee on administrivia, and then the committee breaks up -into working groups (Core, Library, and Enhancements). The full committee also -gets together later in the week to hear working group progress reports.
-The working groups are where most technical activities take place. Each -active issue that appears on an issues list is discussed, as are papers from the -mailing. Most issues are non-controversial and disposed of in a few minutes. -Technical discussions are often led by long-term committee members, often -referring to past decisions or longstanding working group practice. Sometimes a -controversy erupts. It takes first-time attendees awhile to understand the -discussions and how decisions are actually made. The working group chairperson -moderates.
-Sometimes straw polls are taken. In a straw poll anyone attending can vote, -in contrast to the formal votes taken by the full committee, where only voting -members can vote.
-Lunch break is an hour and a half. Informal subgroups often lunch -together; a lot of technical problems are discussed or actually solved at lunch, -or later at dinner. In many ways these discussions involving only a few people -are the most interesting. Sometimes during the regular meetings, a working group -chair will break off a sub-group to tackle a difficult problem.
-Do I have to stay at the main hotel? No, and committee members on -tight budgets often stay at other, cheaper, hotels. (The main hotels are usually -chosen because they have large meeting rooms available, and thus tend to be pricey.) -The advantage of staying at the main hotel is that it is then easier to -participate in the off-line discussions which can be at least as interesting -as what actually happens in the scheduled meetings.
-What do people wear at meetings? Programmer casual. No neckties -to be seen.
-What should I bring to a meeting? It is almost essential to have a -laptop computer along. There is a committee LAN with a wiki and Internet connectivity. -Wireless connectivity has become the norm, although there is usually a wired hub -or two for those needed wired access.
-What should I do to prepare for a meeting? It is helpful to have -downloaded the mailing or individual papers for the -meeting, and read any papers you are interested in. Familiarize yourself with -the issues lists if you haven't done so already. Decide which of the working -groups you want to attend.
-What is a "Paper"? An electronic document containing issues, -proposals, or anything else the committee is interested in. Very little gets -discussed at a meeting, much less acted upon, unless it is presented in a paper. -Papers are available -to anyone. Papers don't just appear randomly; they become available four (lately -six) times a -year, before and after each meeting. Committee members often refer to a paper by -saying what mailing it was in: "See the pre-Redmond mailing."
-What is a "Mailing"? A mailing is the -set of papers prepared four to six times a year before and after each meeting, -or between meetings. It -is physically just a -.zip or .gz -archive of -all the papers for a meeting. Although the mailing's archive file itself is only available to committee members and technical -experts, the contents (except copies of the standard) are available to the -general public as individual papers. The ways of ISO are -inscrutable.
-What is a "Reflector"? The committee's mailing lists are -called "reflectors". There are a number of them; "all", "core", "lib", and "ext" -are the main ones. As a courtesy, Boost technical experts can be added to -committee reflectors at the request of a committee member.
-Revised -April 17, 2005
-© Copyright Beman Dawes, 2002
-- Distributed under the Boost Software License, Version 1.0. (See - accompanying file LICENSE_1_0.txt or copy - at http://www.boost.org/LICENSE_1_0.txt) -
- - - - diff --git a/error_handling.html b/error_handling.html deleted file mode 100644 index 1436404..0000000 --- a/error_handling.html +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - -The following paper is a good introduction to some of the issues of - writing robust generic components:
- -- D. Abrahams: ``Exception Safety - in Generic Components'', originally published in M. - Jazayeri, R. Loos, D. Musser (eds.): Generic Programming, Proc. of a - Dagstuhl Seminar, Lecture Notes on Computer Science. Volume. 1766 -- -
The simple answer is: ``whenever the semantic and performance - characteristics of exceptions are appropriate.''
- -An oft-cited guideline is to ask yourself the question ``is this an - exceptional (or unexpected) situation?'' This guideline has an attractive - ring to it, but is usually a mistake. The problem is that one person's - ``exceptional'' is another's ``expected'': when you really look at the - terms carefully, the distinction evaporates and you're left with no - guideline. After all, if you check for an error condition, then in some - sense you expect it to happen, or the check is wasted code.
- -A more appropriate question to ask is: ``do we want stack - unwinding here?'' Because actually handling an exception is likely - to be significantly slower than executing mainline code, you - should also ask: ``Can I afford stack unwinding here?'' For - example, a desktop application performing a long computation might - periodically check to see whether the user had pressed a cancel - button. Throwing an exception could allow the operation to be - cancelled gracefully. On the other hand, it would probably be - inappropriate to throw and handle exceptions in the inner - loop of this computation because that could have a significant - performance impact. The guideline mentioned above has a grain of - truth in it: in time critical code, throwing an exception - should be the exception, not the rule.
- -std::exception
. Except in *very* rare
- circumstances where you can't afford the cost of a virtual
- table,
- std::exception
makes a reasonable exception base class,
- and when used universally, allows programmers to catch "everything"
- without resorting to catch(...)
. For more about
- catch(...)
, see below.
-
- -#include <iostream> -struct my_exc1 : std::exception { char const* what() const throw(); }; -struct my_exc2 : std::exception { char const* what() const throw(); }; -struct your_exc3 : my_exc1, my_exc2 {}; - -int main() -{ - try { throw your_exc3(); } - catch(std::exception const& e) {} - catch(...) { std::cout << "whoops!" << std::endl; } -} -- -The program above prints
"whoops"
because the
-C++ runtime can't resolve which exception
instance to
-match in the first catch clause.
-
- -- --throw some_exception(); --
There are various ways to avoid copying string objects when - exceptions are copied, including embedding a fixed-length buffer in - the exception object, or managing strings via reference-counting. - However, consider the next point before pursuing either of these - approaches.
-what()
message on demand, if you
- feel you really must format the message. Formatting an exception error
- message is typically a memory-intensive operation that could
- potentially throw an exception. This is an operation best delayed until
- after stack unwinding has occurred, and presumably, released some
- resources. It's a good idea in this case to protect your
- what()
function with a catch(...)
block so
- that you have a fallback in case the formatting code throwswhat()
- message. It's nice to have a message that a programmer stands a
- chance of figuring out, but you're very unlikely to be able to compose
- a relevant and user-comprehensible error message at the point an
- exception is thrown. Certainly, internationalization is beyond the
- scope of the exception class author. Peter Dimov makes an excellent argument
- that the proper use of a what()
string is to serve as a
- key into a table of error message formatters. Now if only we could get
- standardized what()
strings for exceptions thrown by the
- standard library...what()
message is likely to mean that you neglect to
- expose information someone might need in order to make a coherent
- message for users. For example, if your exception reports a numeric
- range error, it's important to have the actual numbers involved
- available as numbers in the exception class' public interface
- where error reporting code can do something intelligent with them. If
- you only expose a textual representation of those numbers in the
- what()
string, you will make life very difficult for
- programmers who need to do something more (e.g. subtraction) with them
- than dumb output.As a developer, if I have violated a precondition of a library I'm - using, I don't want stack unwinding. What I want is a core dump or the - equivalent - a way to inspect the state of the program at the exact point - where the problem was detected. That usually means assert() or - something like it.
- -Sometimes it is necessary to have resilient APIs which can stand up to - nearly any kind of client abuse, but there is usually a significant cost - to this approach. For example, it usually requires that each object used - by a client be tracked so that it can be checked for validity. If you - need that sort of protection, it can usually be provided as a layer on - top of a simpler API. Beware half-measures, though. An API which promises - resilience against some, but not all abuse is an invitation to disaster. - Clients will begin to rely on the protection and their expectations will - grow to cover unprotected parts of the interface.
- -Note for Windows developers: unfortunately, the native
- exception-handling used by most Windows compilers actually throws an
- exception when you use assert(). Actually, this is true of other
- programmer errors such as segmentation faults and divide-by-zero errors.
- One problem with this is that if you use JIT (Just In Time) debugging,
- there will be collateral exception-unwinding before the debugger comes up
- because catch(...)
will catch these not-really-C++
- exceptions. Fortunately, there is a simple but little-known workaround,
- which is to use the following incantation:
-- This technique doesn't work if the SEH is raised from within a catch - block (or a function called from within a catch block), but it still - eliminates the vast majority of JIT-masking problems. - --extern "C" void straight_to_debugger(unsigned int, EXCEPTION_POINTERS*) -{ - throw; -} -extern "C" void (*old_translator)(unsigned, EXCEPTION_POINTERS*) - = _set_se_translator(straight_to_debugger); --
Often the best way to deal with exceptions is to not handle them at - all. If you can let them pass through your code and allow destructors to - handle cleanup, your code will be cleaner.
- -catch(...)
when possible_set_se_translator
hack described above. The result is that
- catch(...)
can have the effect of making some unexpected
- system notification at a point where recovery is impossible look just
- like a C++ exception thrown from a reasonable place, invalidating the
- usual safe assumptions that destructors and catch blocks have taken valid
- steps to ensure program invariants during unwinding.
-
- I reluctantly concede this point to Hillel Y. Sims, after many
- long debates in the newsgroups: until all OSes are "fixed", if
- every exception were derived from std::exception
and
- everyone substituted
- catch(std::exception&)
for catch(...)
, the
- world would be a better place.
Sometimes, catch(...)
, is still the most appropriate
- pattern, in spite of bad interactions with OS/platform design choices. If
- you have no idea what kind of exception might be thrown and you really
- must stop unwinding it's probably still your best bet. One obvious
- place where this occurs is at language boundaries.
© Copyright David Abrahams 2001-2003. All rights reserved.
- -Revised - - 21 August, 2003 -
- - - diff --git a/generic_exception_safety.html b/generic_exception_safety.html deleted file mode 100644 index 1d03d93..0000000 --- a/generic_exception_safety.html +++ /dev/null @@ -1,683 +0,0 @@ - - - - -Lessons Learned from Specifying Exception-Safety - for the C++ Standard Library - -
Abstract. This paper represents the knowledge accumulated in - response to a real-world need: that the C++ Standard Template Library - exhibit useful and well-defined interactions with exceptions, the - error-handling mechanism built-in to the core C++ language. It explores the - meaning of exception-safety, reveals surprising myths about exceptions and - genericity, describes valuable tools for reasoning about program - correctness, and outlines an automated testing procedure for verifying - exception-safety. - -
Keywords: exception-safety, exceptions, STL, C++ - -
Informally, exception-safety in a component means that it exhibits - reasonable behavior when an exception is thrown during its execution. For - most people, the term ``reasonable'' includes all the usual - expectations for error-handling: that resources should not be leaked, and - that the program should remain in a well-defined state so that execution - can continue. For most components, it also includes the expectation that - when an error is encountered, it is reported to the caller. - -
More formally, we can describe a component as minimally exception-safe - if, when exceptions are thrown from within that component, its invariants - are intact. Later on we'll see that at least three different levels of - exception-safety can be usefully distinguished. These distinctions can help - us to describe and reason about the behavior of large systems. - -
In a generic component, we usually have an additional expectation of - exception-neutrality, which means that exceptions thrown by a - component's type parameters should be propagated, unchanged, to the - component's caller. - -
Exception-safety seems straightforward so far: it doesn't constitute - anything more than we'd expect from code using more traditional - error-handling techniques. It might be worthwhile, however, to examine the - term from a psychological viewpoint. Nobody ever spoke of - ``error-safety'' before C++ had exceptions. - -
It's almost as though exceptions are viewed as a mysterious - attack on otherwise correct code, from which we must protect ourselves. - Needless to say, this doesn't lead to a healthy relationship with error - handling! During standardization, a democratic process which requires broad - support for changes, I encountered many widely-held superstitions. In order - to even begin the discussion of exception-safety in generic components, it - may be worthwhile confronting a few of them. - -
``Interactions between templates and exceptions are not - well-understood.'' This myth, often heard from those who consider - these both new language features, is easily disposed of: there simply are - no interactions. A template, once instantiated, works in all respects like - an ordinary class or function. A simple way to reason about the behavior of - a template with exceptions is to think of how a specific instantiation of - that template works. Finally, the genericity of templates should not cause - special concern. Although the component's client supplies part of the - operation (which may, unless otherwise specified, throw arbitrary - exceptions), the same is true of operations using familiar virtual - functions or simple function pointers. - -
``It is well known to be impossible to write an exception-safe - generic container.'' This claim is often heard with reference to - an article by Tom Cargill [4] - in which he explores the problem of exception-safety for a generic stack - template. In his article, Cargill raises many useful questions, but - unfortunately fails to present a solution to his problem.1 - He concludes by suggesting that a solution may not be possible. - Unfortunately, his article was read by many as ``proof'' of that - speculation. Since it was published there have been many examples of - exception-safe generic components, among them the C++ standard library - containers. - -
``Dealing with exceptions will slow code down, and templates are - used specifically to get the best possible performance.'' A good - implementation of C++ will not devote a single instruction cycle to dealing - with exceptions until one is thrown, and then it can be handled at a speed - comparable with that of calling a function [7]. - That alone gives programs using exceptions performance equivalent to that - of a program which ignores the possibility of errors. Using exceptions can - actually result in faster programs than ``traditional'' error - handling methods for other reasons. First, a catch block clearly indicates - to the compiler which code is devoted to error-handling; it can then be - separated from the usual execution path, improving locality of reference. - Second, code using ``traditional'' error handling must typically - test a return value for errors after every single function call; using - exceptions completely eliminates that overhead. - -
``Exceptions make it more difficult to reason about a program's - behavior.'' Usually cited in support of this myth is the way - ``hidden'' execution paths are followed during stack-unwinding. - Hidden execution paths are nothing new to any C++ programmer who expects - local variables to be destroyed upon returning from a function: - -
-- -ErrorCode f( int& result ) // 1 -{ // 2 - X x; // 3 - ErrorCode err = x.g( result ); // 4 - if ( err != kNoError ) // 5 - return err; // 6 - // ...More code here... - return kNoError; // 7 -} --
In the example above, there is a ``hidden'' call to
- X::~X()
in lines 6 and 7. Granted, using exceptions, there is
- no code devoted to error handling visible:
-
-
-- -int f() // 1 -{ // 2 - X x; // 3 - int result = x.g(); // 4 - // ...More code here... - return result; // 5 -} --
For many programmers more familiar with exceptions, the second example - is actually more readable and understandable than the first. The - ``hidden'' code paths include the same calls to destructors of - local variables. In addition, they follow a simple pattern which acts - exactly as though there were a potential return statement after each - function call in case of an exception. Readability is enhanced because the - normal path of execution is unobscured by error-handling, and return values - are freed up to be used in a natural way. - -
There is an even more important way in which exceptions can enhance
- correctness: by allowing simple class invariants. In the first example, if
- x
's constructor should need to allocate resources, it has no
- way to report a failure: in C++, constructors have no return values. The
- usual result when exceptions are avoided is that classes requiring
- resources must include a separate initializer function which finishes the
- job of construction. The programmer can therefore never be sure, when an
- object of class X
is used, whether he is handling a
- full-fledged X
or some abortive attempt to construct one (or
- worse: someone simply forgot to call the initializer!)
-
-
A non-generic component can be described as exception-safe in isolation, - but because of its configurability by client code, exception-safety in a - generic component usually depends on a contract between the component and - its clients. For example, the designer of a generic component might require - that an operation which is used in the component's destructor not throw any - exceptions.2 - The generic component might, in return, provide one of the following - guarantees: - -
The basic guarantee is a simple minimum standard for exception-safety to - which we can hold all components. It says simply that after an exception, - the component can still be used as before. Importantly, the preservation of - invariants allows the component to be destroyed, potentially as part of - stack-unwinding. This guarantee is actually less useful than it might at - first appear. If a component has many valid states, after an exception we - have no idea what state the component is in|only that the state is valid. - The options for recovery in this case are limited: either destruction or - resetting the component to some known state before further use. Consider - the following example: - -
-- -template <class X> -void print_random_sequence() -{ - std::vector<X> v(10); // A vector of 10 items - try { - // Provides only the basic guarantee - v.insert( v.begin(), X() ); - } - catch(...) {} // ignore any exceptions above - // print the vector's contents - std::cout "(" << v.size() << ") "; - std::copy( v.begin(), v.end(), - std::ostream_iterator<X>( std::cout, " " ) ); -} --
Since all we know about v after an exception is that it is valid, the
- function is allowed to print any random sequence of X
s.3
- It is ``safe'' in the sense that it is not allowed to crash, but
- its output may be unpredictable.
-
-
The strong guarantee provides full - ``commit-or-rollback'' semantics. In the case of C++ standard - containers, this means, for example, that if an exception is thrown all - iterators remain valid. We also know that the container has exactly the - same elements as before the exception was thrown. A transaction that has no - effects if it fails has obvious benefits: the program state is simple and - predictable in case of an exception. In the C++ standard library, nearly - all of the operations on the node-based containers list, set, multiset, - map, and multimap provide the strong guarantee.4). - -
The no-throw guarantee is the strongest of all, and it says that - an operation is guaranteed not to throw an exception: it always completes - successfully. This guarantee is necessary for most destructors, and indeed - the destructors of C++ standard library components are all guaranteed not - to throw exceptions. The no-throw guarantee turns out to be - important for other reasons, as we shall see.5 - -
Inevitably, the contract can get more complicated: a quid pro quo
- arrangement is possible. Some components in the C++ Standard Library give
- one guarantee for arbitrary type parameters, but give a stronger guarantee
- in exchange for additional promises from the client type that no exceptions
- will be thrown. For example, the standard container operation
- vector<T>::erase
gives the basic guarantee for
- any T
, but for types whose copy constructor and copy
- assignment operator do not throw, it gives the no-throw guarantee.6
-
-
From a client's point-of-view, the strongest possible level of safety
- would be ideal. Of course, the no-throw guarantee is simply
- impossible for many operations, but what about the strong guarantee?
- For example, suppose we wanted atomic behavior for
- vector<T>::insert
. Insertion into the middle of a vector
- requires copying elements after the insertion point into later positions,
- to make room for the new element. If copying an element can fail, rolling
- back the operation would require ``undoing'' the previous
- copies...which depends on copying again. If copying back should fail (as it
- likely would), we have failed to meet our guarantee.
-
-
One possible alternative would be to redefine insert
to
- build the new array contents in a fresh piece of memory each time, and only
- destroy the old contents when that has succeeded. Unfortunately, there is a
- non-trivial cost if this approach is followed: insertions near the end of a
- vector which might have previously caused only a few copies would now cause
- every element to be copied. The basic guarantee is a
- ``natural'' level of safety for this operation, which it can
- provide without violating its performance guarantees. In fact all of the
- operations in the library appear to have such a ``natural'' level
- of safety.
-
-
Because performance requirements were already a well-established part of - the draft standard and because performance is a primary goal of the STL, - there was no attempt to specify more safety than could be provided within - those requirements. Although not all of the library gives the strong - guarantee, almost any operation on a standard container which gives the - basic guarantee can be made strong using the ``make a - new copy'' strategy described above: - -
-- -template <class Container, class BasicOp> -void MakeOperationStrong( Container& c, const BasicOp& op ) -{ - Container tmp(c); // Copy c - op(tmp); // Work on the copy - c.swap(tmp); // Cannot fail7 -} --
This technique can be folded into a wrapper class to make a similar - container which provides stronger guarantees (and different performance - characteristics).8 - -
By considering a particular implementation, we can hope to discern a - natural level of safety. The danger in using this to establish requirements - for a component is that the implementation might be restricted. If someone - should come up with a more-efficient implementation which we'd like to use, - we may find that it's incompatible with our exception-safety requirements. - One might expect this to be of no concern in the well-explored domains of - data structures and algorithms covered by the STL, but even there, advances - are being made. A good example is the recent introsort algorithm [6], - which represents a substantial improvement in worst-case complexity over - the well-established quicksort. - -
To determine exactly how much to demand of the standard components, I - looked at a typical real-world scenario. The chosen test case was a - ``composite container.'' Such a container, built of two or more - standard container components, is not only commonly needed, but serves as a - simple representative case for maintaining invariants in larger systems: - -
-- -// SearchableStack - A stack which can be efficiently searched -// for any value. -template <class T> -class SearchableStack -{ - public: - void push(const T& t); // O(log n) - void pop(); // O(log n) - bool contains(const T& t) const; // O(log n) - const T& top() const; // O(1) - private: - std::set<T> set_impl; - std::list<std::set<T>::iterator> list_impl; -}; --
The idea is that the list acts as a stack of set iterators: every - element goes into the set first, and the resulting position is pushed onto - the list. The invariant is straightforward: the set and the list should - always have the same number of elements, and every element of the set - should be referenced by an element of the list. The following - implementation of the push function is designed to give the strong - guarantee within the natural levels of safety provided by set and list: - -
-- -template <class T> // 1 -void SearchableStack<T>::push(const T& t) // 2 -{ // 3 - set<T>::iterator i = set_impl.insert(t); // 4 - try // 5 - { // 6 - list_impl.push_back(i); // 7 - } // 8 - catch(...) // 9 - { // 10 - set_impl.erase(i); // 11 - throw; // 12 - } // 13 -} // 14 --
What does our code actually require of the library? We need to examine - the lines where non-const operations occur: - -
set_impl
is modified
- in the process, our invariant is violated. We need to be able to rely on
- the strong guarantee from set<T>::insert
.
-
- push_back
fails, but
- list_impl
is modified in the process, our invariant is
- violated, so we need to be able to rely on the strong guarantee
- from list<T>::insert.
-
- set<T>::erase
.9
-
- i
to the erase
function: we need the
- no-throw guarantee from the copy constructor of
- set<T>::iterator
.
- I learned a great deal by approaching the question this way during - standardization. First, the guarantee specified for the composite container - actually depends on stronger guarantees from its components (the - no-throw guarantees in line 11). Also, I took advantage of all of - the natural level of safety to implement this simple example. Finally, the - analysis revealed a requirement on iterators which I had previously - overlooked when operations were considered on their own. The conclusion was - that we should provide as much of the natural level of safety as possible. - Faster but less-safe implementations could always be provided as extensions - to the standard components. 10 - -
As part of the standardization process, I produced an exception-safe - reference implementation of the STL. Error-handling code is seldom - rigorously tested in real life, in part because it is difficult to cause - error conditions to occur. It is very common to see error-handling code - which crashes the first time it is executed ...in a shipping product! To - bolster confidence that the implementation actually worked as advertised, I - designed an automated test suite, based on an exhaustive technique due to - my colleague Matt Arnold. - -
The test program started with the basics: reinforcement and
- instrumentation, especially of the global operators new
and
- delete
.11Instances of the components (containers and
- algorithms) were created, with type parameters chosen to reveal as many
- potential problems as possible. For example, all type parameters were given
- a pointer to heap-allocated memory, so that leaking a contained object
- would be detected as a memory leak.
-
-
Finally, a scheme was designed that could cause an operation to throw an
- exception at each possible point of failure. At the beginning of every
- client-supplied operation which is allowed to throw an exception, a call to
- ThisCanThrow
was added. A call to ThisCanThrow
- also had to be added everywhere that the generic operation being tested
- might throw an exception, for example in the global operator
- new
, for which an instrumented replacement was supplied.
-
-
-- -// Use this as a type parameter, e.g. vector<TestClass> -struct TestClass -{ - TestClass( int v = 0 ) - : p( ThisCanThrow(), new int( v ) ) {} - TestClass( const TestClass& rhs ) - : p( ThisCanThrow(), new int( *rhs.p ) ) {} - const TestClass& operator=( const TestClass& rhs ) - { ThisCanThrow(); *p = *rhs.p; } - bool operator==( const TestClass& rhs ) const - { ThisCanThrow(); return *p == *rhs.p; } - ...etc... - ~TestClass() { delete p; } -}; --
ThisCanThrow
simply decrements a ``throw
- counter'' and, if it has reached zero, throws an exception. Each test
- takes a form which begins the counter at successively higher values in an
- outer loop and repeatedly attempts to complete the operation being tested.
- The result is that the operation throws an exception at each successive
- step along its execution path that can possibly fail. For example, here is
- a simplified version of the function used to test the strong
- guarantee: 12
-
-
-- -extern int gThrowCounter; // The throw counter -void ThisCanThrow() -{ - if (gThrowCounter-- == 0) - throw 0; -} - -template <class Value, class Operation> -void StrongCheck(const Value& v, const Operation& op) -{ - bool succeeded = false; - for (long nextThrowCount = 0; !succeeded; ++nextThrowCount) - { - Value duplicate = v; - try - { - gThrowCounter = nextThrowCount; - op( duplicate ); // Try the operation - succeeded = true; - } - catch(...) // Catch all exceptions - { - bool unchanged = duplicate == v; // Test strong guarantee - assert( unchanged ); - } - // Specialize as desired for each container type, to check - // integrity. For example, size() == distance(begin(),end()) - CheckInvariant(v); // Check any invariant - } -} --
Notably, this kind of testing is much easier and less intrusive with a
- generic component than with non-generics, because testing-specific type
- parameters can be used without modifying the source code of the component
- being tested. Also, generic functions like StrongCheck
above
- were instrumental in performing the tests on a wide range of values and
- operations.
-
-
The description of exception-safety in the C++ Standard [1] - is only slightly more formal, but relies on hard-to-read - ``standardese'' and an occasionally subtle web of implication.13 - In particular, leaks are not treated directly at all. It does have the - advantage that it is the standard. - -
The original reference implementation [5] - of the exception-safe STL is an adaptation of an old version of the SGI - STL, designed for C++ compilers with limited features. Although it is not a - complete STL implementation, the code may be easier to read, and it - illustrates a useful base-class technique for eliminating - exception-handling code in constructors. The full test suite [3] - used to validate the reference implementation has been used successfully to - validate all recent versions of the SGI STL, and has been adapted to test - one other vendor's implementation (which failed). As noted on the - documentation page, it also seems to have the power to reveal hidden - compiler bugs, particularly where optimizers interact with - exception-handling code. - -
1 Probably the greatest impediment to a solution - in Cargill's case was an unfortunate combination of choices on his part: - the interface he chose for his container was incompatible with his - particular demands for safety. By changing either one he might have solved - the problem. - -
2 It is usually inadvisable to throw an - exception from a destructor in C++, since the destructor may itself be - called during the stack-unwinding caused by another exception. If the - second exception is allowed to propagate beyond the destructor, the program - is immediately terminated. - -
3 In practice of course, this function would - make an extremely poor random sequence generator! - -
4 It is worth noting that mutating algorithms
- usually cannot provide the strong guarantee: to roll back a modified
- element of a range, it must be set back to its previous value using
- operator=
, which itself might throw. In the C++ standard
- library, there are a few exceptions to this rule, whose rollback behavior
- consists only of destruction: uninitialized_copy
,
- uninitialized_fill
, and uninitialized_fill_n
.
-
-
5 All type parameters supplied by clients of the - C++ standard library are required not to throw from their destructors. In - return, all components of the C++ standard library provide at least the - basic guarantee. - -
6 Similar arrangements might have been made in - the C++ standard for many of the mutating algorithms, but were never - considered due to time constraints on the standardization process. - -
7 Associative containers whose
- Compare
object might throw an exception when copied cannot use
- this technique, since the swap function might fail.
-
-
8 This suggests another potential use for the
- oft-wished-for but as yet unseen container_traits<>
- template: automated container selection to meet exception-safety
- constraints.
-
-
9 One might be tempted to surround the erase
- operation with a try
/catch
block to reduce the
- requirements on set<T>
and the problems that arise in
- case of an exception, but in the end that just begs the question. First,
- erase just failed and in this case there are no viable alternative ways to
- produce the necessary result. Second and more generally, because of the
- variability of its type parameters a generic component can seldom be
- assured that any alternatives will succeed.
-
-
10 The prevalent philosophy in the design of - STL was that functionality that wasn't essential to all uses should be left - out in favor of efficiency, as long as that functionality could be obtained - when needed by adapting the base components. This departs from that - philosophy, but it would be difficult or impossible to obtain even the - basic guarantee by adapting a base component that doesn't already - have it. - -
11 An excellent discussion on how to fortify - memory subsystems can be found in: Steve Maguire, Writing Solid Code, - Microsoft Press, Redmond, WA, 1993, ISBN 1-55615- 551-4. - -
12 Note that this technique requires that the - operation being tested be exception-neutral. If the operation ever tries to - recover from an exception and proceed, the throw counter will be negative, - and subsequent operations that might fail will not be tested for - exception-safety. - -
13 The changes to the draft standard which - introduced exception-safety were made late in the process, when amendments - were likely to be rejected solely on the basis of the number of altered - words. Unfortunately, the result compromises clarity somewhat in favor of - brevity. Greg Colvin was responsible for the clever language-lawyering - needed to minimize the extent of these changes. diff --git a/generic_programming.html b/generic_programming.html deleted file mode 100644 index 165dc18..0000000 --- a/generic_programming.html +++ /dev/null @@ -1,451 +0,0 @@ - - - -
- - - - - -This is an incomplete survey of some of the generic programming - techniques used in the boost libraries.
- -Generic programming is about generalizing software components so that - they can be easily reused in a wide variety of situations. In C++, class - and function templates are particularly effective mechanisms for generic - programming because they make the generalization possible without - sacrificing efficiency.
- -As a simple example of generic programming, we will look at how one
- might generalize the memcpy() function of the C standard
- library. An implementation of memcpy() might look like the
- following:
-
-
-- The memcpy() function is already generalized to some extent by - the use of void* so that the function can be used to copy arrays - of different kinds of data. But what if the data we would like to copy is - not in an array? Perhaps it is in a linked list. Can we generalize the - notion of copy to any sequence of elements? Looking at the body of - memcpy(), the function's minimal requirements are - that it needs to traverse through the sequence using some sort - of pointer, access elements pointed to, write the elements - to the destination, and compare pointers to know when to stop. The - C++ standard library groups requirements such as these into - concepts, in this case the Input Iterator - concept (for region2) and the Output Iterator - concept (for region1). - -void* memcpy(void* region1, const void* region2, size_t n) -{ - const char* first = (const char*)region2; - const char* last = ((const char*)region2) + n; - char* result = (char*)region1; - while (first != last) - *result++ = *first++; - return result; -} --
If we rewrite the memcpy() as a function template, and use
- the Input
- Iterator and Output Iterator
- concepts to describe the requirements on the template parameters, we can
- implement a highly reusable copy() function in the following
- way:
-
-
-- -template <typename InputIterator, typename OutputIterator> -OutputIterator -copy(InputIterator first, InputIterator last, OutputIterator result) -{ - while (first != last) - *result++ = *first++; - return result; -} --
Using the generic copy() function, we can now copy elements
- from any kind of sequence, including a linked list that exports iterators
- such as std::list.
-
-
-- -#include <list> -#include <vector> -#include <iostream> - -int main() -{ - const int N = 3; - std::vector<int> region1(N); - std::list<int> region2; - - region2.push_back(1); - region2.push_back(0); - region2.push_back(3); - - std::copy(region2.begin(), region2.end(), region1.begin()); - - for (int i = 0; i < N; ++i) - std::cout << region1[i] << " "; - std::cout << std::endl; -} --
The concepts used in the C++ Standard Library are documented at the SGI STL - site.
- -A traits class provides a way of associating information with a - compile-time entity (a type, integral constant, or address). For example, - the class template std::iterator_traits<T> - looks something like this:
- --- The traits' value_type gives generic code the type which the - iterator is "pointing at", while the iterator_category can be - used to select more efficient algorithms depending on the iterator's - capabilities. - -template <class Iterator> -struct iterator_traits { - typedef ... iterator_category; - typedef ... value_type; - typedef ... difference_type; - typedef ... pointer; - typedef ... reference; -}; --
A key feature of traits templates is that they're - non-intrusive: they allow us to associate information with - arbitrary types, including built-in types and types defined in - third-party libraries, Normally, traits are specified for a particular - type by (partially) specializing the traits template.
- -For an in-depth description of std::iterator_traits, see this page - provided by SGI. Another very different expression of the traits idiom in - the standard is std::numeric_limits<T> which provides - constants describing the range and capabilities of numeric types.
- -Tag dispatching is a way of using function overloading to - dispatch based on properties of a type, and is often used hand in - hand with traits classes. A good example of this synergy is the - implementation of the std::advance() - function in the C++ Standard Library, which increments an iterator - n times. Depending on the kind of iterator, there are different - optimizations that can be applied in the implementation. If the iterator - is random - access (can jump forward and backward arbitrary distances), then the - advance() function can simply be implemented with i += - n, and is very efficient: constant time. Other iterators must be - advanced in steps, making the operation linear in n. If the - iterator is bidirectional, - then it makes sense for n to be negative, so we must decide - whether to increment or decrement the iterator.
- -The relation between tag dispatching and traits classes is that the - property used for dispatching (in this case the - iterator_category) is often accessed through a traits class. The - main advance() function uses the iterator_traits - class to get the iterator_category. It then makes a call the the - overloaded advance_dispatch() function. The appropriate - advance_dispatch() is selected by the compiler based on whatever - type the iterator_category resolves to, either input_iterator_tag, - bidirectional_iterator_tag, - or random_access_iterator_tag. - A tag is simply a class whose only purpose is to convey - some property for use in tag dispatching and similar techniques. Refer to - this page - for a more detailed description of iterator tags.
- --- -namespace std { - struct input_iterator_tag { }; - struct bidirectional_iterator_tag { }; - struct random_access_iterator_tag { }; - - namespace detail { - template <class InputIterator, class Distance> - void advance_dispatch(InputIterator& i, Distance n, input_iterator_tag) { - while (n--) ++i; - } - - template <class BidirectionalIterator, class Distance> - void advance_dispatch(BidirectionalIterator& i, Distance n, - bidirectional_iterator_tag) { - if (n >= 0) - while (n--) ++i; - else - while (n++) --i; - } - - template <class RandomAccessIterator, class Distance> - void advance_dispatch(RandomAccessIterator& i, Distance n, - random_access_iterator_tag) { - i += n; - } - } - - template <class InputIterator, class Distance> - void advance(InputIterator& i, Distance n) { - typename iterator_traits<InputIterator>::iterator_category category; - detail::advance_dispatch(i, n, category); - } -} --
An adaptor is a class template which builds on another type or - types to provide a new interface or behavioral variant. Examples of - standard adaptors are std::reverse_iterator, - which adapts an iterator type by reversing its motion upon - increment/decrement, and std::stack, which adapts a - container to provide a simple stack interface.
- -A more comprehensive review of the adaptors in the standard can be - found - here.
- -Note: The type generator concept has largely been - superseded by the more refined notion of a metafunction. See - C++ Template - Metaprogramming for an in-depth discussion of metafunctions.
- -A type generator is a template whose only purpose is to - synthesize a new type or types based on its template argument(s)[1]. The generated type is usually expressed as a nested typedef - named, appropriately type. A type generator is usually used to - consolidate a complicated type expression into a simple one. This example - uses an old version of iterator_adaptor - whose design didn't allow derived iterator types. As a result, every - adapted iterator had to be a specialization of iterator_adaptor - itself and generators were a convenient way to produce those types.
- --- -template <class Predicate, class Iterator, - class Value = complicated default, - class Reference = complicated default, - class Pointer = complicated default, - class Category = complicated default, - class Distance = complicated default - > -struct filter_iterator_generator { - typedef iterator_adaptor< - - Iterator,filter_iterator_policies<Predicate,Iterator>, - Value,Reference,Pointer,Category,Distance> type; -}; --
Now, that's complicated, but producing an adapted filter iterator - using the generator is much easier. You can usually just write:
- --- -boost::filter_iterator_generator<my_predicate,my_base_iterator>::type --
An object generator is a function template whose only purpose - is to construct a new object out of its arguments. Think of it as a kind - of generic constructor. An object generator may be more useful than a - plain constructor when the exact type to be generated is difficult or - impossible to express and the result of the generator can be passed - directly to a function rather than stored in a variable. Most Boost - object generators are named with the prefix "make_", after - std::make_pair(const T&, const U&).
- -For example, given:
- --- By chaining two standard object generators, std::bind2nd() - and std::mem_fun(), - we can easily tweak all widgets: - -struct widget { - void tweak(int); -}; -std::vector<widget *> widget_ptrs; --
-- -void tweak_all_widgets1(int arg) -{ - for_each(widget_ptrs.begin(), widget_ptrs.end(), - bind2nd(std::mem_fun(&widget::tweak), arg)); -} --
Without using object generators the example above would look like - this:
- --- -void tweak_all_widgets2(int arg) -{ - for_each(struct_ptrs.begin(), struct_ptrs.end(), - std::binder2nd<std::mem_fun1_t<void, widget, int> >( - std::mem_fun1_t<void, widget, int>(&widget::tweak), arg)); -} --
As expressions get more complicated the need to reduce the verbosity - of type specification gets more compelling.
- -A policy class is a template parameter used to transmit behavior. An - example from the standard library is std::allocator, - which supplies memory management behaviors to standard containers.
- -Policy classes have been explored in detail by Andrei Alexandrescu in this chapter - of his book, Modern C++ Design. He writes:
- --- -In brief, policy-based class design fosters assembling a class with - complex behavior out of many little classes (called policies), each of - which takes care of only one behavioral or structural aspect. As the - name suggests, a policy establishes an interface pertaining to a - specific issue. You can implement policies in various ways as long as - you respect the policy interface.
- -Because you can mix and match policies, you can achieve a - combinatorial set of behaviors by using a small core of elementary - components.
-
Andrei's description of policy classes suggests that their power is - derived from granularity and orthogonality. Less-granular policy - interfaces have been shown to work well in practice, though. - This paper describes an old version of iterator_adaptor - that used non-orthogonal policies. There is also precedent in the - standard library: std::char_traits, - despite its name, acts as a policies class that determines the behaviors - of std::basic_string.
- -Revised - 06 Nov 2007 -
- -© Copyright David Abrahams 2001.
- -Distributed under the Boost Software License, Version 1.0. See -www.boost.org/LICENSE_1_0.txt
- - - \ No newline at end of file