I l@ve RuBoard Previous Section Next Section

Solution

graphics/bulb_icon.gif

Remember the introduction to the question? It was: Forwarding functions are useful tools for handing off work to another function or object, especially when the hand-off is done efficiently.

This introduction gets to the heart of the matter梕fficiency.

There are two main enhancements that would make this function more efficient. The first should always be done; 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 as recently as 1997, the draft C++ language standard said that because 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 (that is, to eliminate x as unnecessary). For example, if the client code looks something like this:

    
    
    X my_x; 
    
    
    f( my_x );
    
    
    

    then the compiler used to be allowed to either:

    • 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()

    • Pass my_x directly to g() without creating a copy at all, because it notices that the extra copy will never be 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 in which the compiler is allowed to elide "extra" copies like this. This change was necessary to avoid problems that can come up when compilers are permitted to wantonly elide copy construction, especially when copy construction has side effects. There are reasonable cases in which reasonable code may legitimately rely on the number of copies actually made of an object.

      Today, the only situation in which a compiler may still elide copy constructors is for the return value optimization (see your favorite textbook for details) and for temporary objects. This means that for forwarding functions like f(), a compiler is now required to perform two copies. Because 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.

      Guideline

      graphics/guideline_icon.gif

      Prefer passing objects by reference instead of by value, using const wherever possible.


      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梐void the dusty corners of the language as much as you can, and strive never to rely on cute subtleties.

      Guideline

      graphics/guideline_icon.gif

      Avoid the "dusty corners" of a language; use the simplest techniques that are effective.


  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.

Guideline

graphics/guideline_icon.gif

Avoid inlining or detailed tuning until performance profiles prove the need.


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 makes 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 because 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 and not inlining can be valid choices. It's a judgment call in which 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.

    I l@ve RuBoard Previous Section Next Section