GotW #27

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book Exceptional C++ (Addison-Wesley, 2000) for the most current solutions to GotW issues #1-30. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard.

Forwarding Functions
Difficulty: 3 / 10

What's the best way to write a forwarding function? The basic answer is easy, but we'll also learn about a recent and subtle change to the language.

Problem

Forwarding functions are useful tools for handing off work to another function or object, especially when the handoff is done efficiently.

Critique the following forwarding function. Would you change it? If so, how?

    // file f.cpp

    #include "f.h"
    /*...*/
    bool f( X x ) {
        return g( x );
    }

(Warning: One purpose of this GotW is to illustrate the consequences of a subtle language refinement made this July [1997] in London.)

Solution

Forwarding functions are useful tools for handing off work to another function or object, especially when the handoff is done efficiently.

This introduction gets to the heart of the matter: efficiency.

Critique the following forwarding function. Would you change it? If so, how?

    // file f.cpp

    #include "f.h"
    /*...*/
    bool f( X x ) {
        return g( x );
    }

There are two main enhancements that would make this function more efficient. The first should always be done, and the second is a matter of judgment.

1. Pass the parameter by const& instead of by value

"Isn't that blindingly obvious?" you might ask. No, it isn't, not in this particular case. Until recently, the language said that, since a compiler can prove that the parameter x will never be used for any other purpose than passing it in turn to g(), the compiler may decide to elide x completely. For example, if the client code looks something like this:

    X my_x;
    f( my_x );

then the compiler might either:

a) create a copy of my_x for f()'s use (this is the parameter named x in f()'s scope) and pass that to g(), or

b) pass my_x directly to g() without creating a copy at all since it notices that the extra copy will never be otherwise used except as a parameter to g().

The latter is nicely efficient, isn't it? That's what optimizing compilers are for, aren't they?

Yes, and yes, until the London meeting in July 1997. At that meeting, the draft was amended to place more restrictions on the situations where the compiler is allowed to elide "extra" copies like this.[1] The only places where a compiler may still elide copy constructors is for the return value optimization (see your favourite textbook for details) and for temporary objects.

This means that, for forwarding functions like f, a compiler is now required to perform two copies. Since we (as the authors of f) know in this case that the extra copy isn't necessary, we should fall back on our general rule and declare x as a const X& parameter.

(Note: If we'd been following this general rule all along instead of trying to take advantage of detailed knowledge about what the compiler is allowed to do, the change in the rules wouldn't have affected us. This is a stellar example of why simpler is better -- avoid the dusty corners of the language as much as you can, and strive to never rely on cute subtleties.)

2. Inline the function

This one is a matter of judgment. In short, prefer to write all functions out-of-line by default, and then selectively inline individual functions as necessary only after you know that the performance gain from inlining is actually needed.

If you inline the function, the positive side is that you avoid the overhead of the extra function call to f.

The negative side is that inlining f exposes f's implementation and make client code depend on it, so that if f changes all client code must recompile. Worse, client code now also needs at least the prototype for function g(), which is a bit of a shame since client code never actually calls g directly and probably never needed g's prototype before (at least, not as far as we can tell from our example). And if g() itself were changed to take other parameters of still other types, client code would now depend on those classes' declarations, too.

Both inlining or not inlining can be valid choices. It's a judgment call where the benefits and drawbacks depend on what you know about how (and how widely) f is used today, and how (and how often) it's likely to change in the future.

From the GotW coding standards:

- prefer passing parameters of class type by const& rather than passing them by value

- avoid inlining functions until profiling tells you it's justified (programmers are notoriously bad at guessing which parts of their code are performance bottlenecks)

 

Notes

1. This change was necessary to avoid the problems that can come up when compilers are permitted to wantonly elide copy construction, especially when copy construction has side effects. There are cases where reasonable code may rely on the number of copies actually made of an object.

Copyright 2009 Herb Sutter