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 @@ - - - - - - - - - - - Counted Body Techniques - - - -

Counted Body Techniques

- -
-

Kevlin Henney
- (kevlin@acm.org, khenney@qatraining.com)

-
- -
-

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....

-
-
- -

And then there were none...

- -
-

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:

- - -
-
- -

Attached vs detached

- -
-

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.

-
-
- -

A requirements based approach

- -
-

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):

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ExpressionReturn typeSemantics and notes
acquire(ptr)no requirementpost: acquired(ptr)
release(ptr)no requirementpre: acquired(ptr)
- post: acquired(ptr) == #acquire - - #release
acquired(ptr)convertible to boolreturn: #acquire > #release
dispose(ptr, ptr)no requirementpre: !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:

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ExpressionReturn typeSemantics and notes
acquire(null)no requirementaction: none
release(null)no requirementaction: none
acquired(null)convertible to boolreturn: false
dispose(null, null)no requirementaction: 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.

-
-
- -

Getting smart

- -
-

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);
-}
-
-
-
-
-
- -

Public accountability

- -
-

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
-
-
-
-
-
- -

Runtime mixin

- -
-

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 *.

-
-
- -

Getting smarter

- -
-

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.

-
-
- -

Conclusion

- -
-

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.

-
- -
-
- First published in Overload 25, - April 1998, ISSN 1354-3172 -
- -

Valid HTML 4.01 Transitional

- -

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 @@ - - - - - - - - -C++ Committee Meetings - - - - -

C++ Committee Meeting FAQ for Boost Members

-

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 @@ - - - - - - - - Error and Exception Handling - - - -

Error and Exception Handling

- -

References

- -

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 -
- -

Guidelines

- -

When should I use exceptions?

- -

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.

- -

How should I design my exception classes?

- -
    -
  1. Derive your exception class - from 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. - -
  2. Use virtual inheritance. This insight is due - to Andrew Koenig. Using virtual inheritance from your - exception's base class(es) prevents ambiguity problems at the - catch-site in case someone throws an exception derived from - multiple bases which have a base class in common: - -
    -#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. - -
  3. - -
  4. - Don't embed a std::string object or any other data - member or base class whose copy constructor could throw an exception. - That could lead directly to std::terminate() at the throw point. - Similarly, it's a bad idea to use a base or member whose ordinary - constructor(s) might throw, because, though not necessarily fatal to - your program, you may report a different exception than intended from - a throw-expression that includes construction such as: - -
    -
    -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.

    -
  5. - -
  6. Format the 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 throws
  7. - -
  8. Don't worry too much about the what() - 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...
  9. - -
  10. Expose relevant information about the cause of the error in - your exception class' public interface. A fixation on the - 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.
  11. - -
  12. Make your exception class immune to double-destruction if - possible. Unfortunately, several popular compilers occasionally cause - exception objects to be destroyed twice. If you can arrange for that to - be harmless (e.g. by zeroing deleted pointers) your code will be more - robust.
  13. -
- -

What About Programmer Errors?

- -

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:

- -
-
-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);
-
-
- 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. - -

How should I handle exceptions?

- -

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.

- -

Avoid catch(...) when possible

- Unfortunately, operating systems other than Windows also wind non-C++ - "exceptions" (such as thread cancellation) into the C++ EH machinery, and - there is sometimes no workaround corresponding to the - _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 @@ - - - - - Exception-Safety in Generic Components - - - -

Exception-Safety in Generic Components

- -

Lessons Learned from Specifying Exception-Safety - for the C++ Standard Library - -

David Abrahams

- -

- david.abrahams@rcn.com

- -

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++ - -

1 What is exception-safety?

- -

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. - -

2 Myths and Superstitions

- -

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!) - -

3 A contractual basis for exception-safety

- -

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 Xs.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 - -

4 Legal Wrangling

- -

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 - -

5 What level of exception-safety should a component specify?

- -

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 - -

6 Should we take everything we can get?

- -

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: - -

- -

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 - -

7 Automated testing for exception-safety

- -

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. - -

8 Further Reading

- To my knowledge, there are currently only two descriptions of STL - exception-safety available. The original specification [2] - for the reference exception-safe implementation of the STL is an informal - specification, simple and self-explanatory (also verbose), and uses the - basic- and strong-guarantee distinctions outlined in this - article. It explicitly forbids leaks, and differs substantively from the - final C++ standard in the guarantees it makes, though they are largely - identical. I hope to produce an updated version of this document soon. - -

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. - -

References

- -
    -
  1. International Standard ISO/IEC 14882, - Information Technology-Programming Languages-C++, Document Number - ISO/IEC 14882-1998, available from http://webstore.ansi.org/ansidocstore/default.asp. - -
  2. D. Abrahams, Exception Safety in - STLport, available at http://www.stlport.org/doc/exception_safety.html. - -
  3. D. Abrahams and B. Fomitchev, Exception - Handling Test Suite, available at http://www.stlport.org/doc/eh_testsuite.html. - -
  4. Tom Cargill, ``Exception Handling: - A False Sense of Security,'' C++ Report, Nov-Dec 1994, also - available at http://www.awprofessional.com/content/images/020163371x/supplements/Exception_Handling_Article.html. - -
  5. B. Fomitchev, Adapted SGI STL Version - 1.0, with exception handling code by D. Abrahams, available at http://www.stlport.org. - -
  6. D. R. Musser, ``Introspective Sorting - and Selection Algorithms,'' Software-Practice and Experience - 27(8):983-993, 1997. - -
  7. Bjarne Stroustrup, The Design And - Evolution of C++. Addison Wesley, Reading, MA, 1995, ISBN - 0-201-54330-3, Section 16.9.1. -
- -

Footnotes

- -

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 @@ - - - - - - - - - - Generic Programming Techniques - - - - boost.png (6897 bytes) - -

Generic Programming Techniques

- -

This is an incomplete survey of some of the generic programming - techniques used in the boost libraries.

- -

Table of Contents

- - - -

Introduction

- -

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:
-
-

- -
-
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;
-}
-
-
- 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). - -

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;
-}
-
-
- -

Anatomy of a Concept

- A concept is a set of requirements - consisting of valid expressions, associated types, invariants, and - complexity guarantees. A type that satisfies the requirements is - said to model the concept. A concept can extend the - requirements of another concept, which is called - refinement. - - - -

The concepts used in the C++ Standard Library are documented at the SGI STL - site.

- -

Traits

- -

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:

- -
-
template <class Iterator>
-struct iterator_traits {
-  typedef ... iterator_category;
-  typedef ... value_type;
-  typedef ... difference_type;
-  typedef ... pointer;
-  typedef ... reference;
-};
-
-
- 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. - -

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

- -

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);
-  }
-}
-
-
- -

Adaptors

- -

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.

- -

Type Generators

- -

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
-
-
- -

Object Generators

- -

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:

- -
-
struct widget {
-  void tweak(int);
-};
-std::vector<widget *> widget_ptrs;
-
-
- By chaining two standard object generators, std::bind2nd() - and std::mem_fun(), - we can easily tweak all widgets: - -
-
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.

- -

Policy Classes

- -

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.

- -

Notes

- [1] Type generators are sometimes viewed as a workaround - for the lack of ``templated typedefs'' in C++. -
- -

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