c++ (Equivalent code?) (fwd)
Kragen Sitaker
kragen@pobox.com
Mon, 24 May 1999 12:12:11 -0400 (EDT)
I now know I will never again voluntarily use C++. :)
---------- Forwarded message ----------
Date: Mon, 24 May 1999 10:13:21 -0400 (EDT)
From: Kragen Sittler <sittler@day.erim-int.com>
To: kragen@pobox.com
Subject: c++ (Equivalent code?) (fwd)
---------- Forwarded message ----------
Date: Mon, 24 May 1999 08:08:53 -0500 (CDT)
From: "Allan D. Clarke" <clarke@ses.com>
To: cpptips@ses.com
Subject: c++ (Equivalent code?)
TITLE: Equivalent Code?
(Newsgroups: comp.lang.c++.moderated, 24 May 99)
SUTTER: Herb Sutter <hsutter@peerdirect.com>
> -------------------------------------------------------------------
> Guru of the Week problems and solutions are posted regularly on
> news:comp.lang.c++.moderated. For past problems and solutions
> see the GotW archive at www.peerdirect.com. (c) 1999 H.P.Sutter
> News archives may keep copies of this article.
> -------------------------------------------------------------------
>
>_______________________________________________________
>
>GotW #55: Equivalent Code?
> (originally proposed by Christoph Schlenker)
>
>Difficulty: 5 / 10
>_______________________________________________________
>
>
>Congratulations to Mark Strecker and Ali Cehreli, the
>Gurus of the Week!
>
>I see that people took the JG question a lot farther
>than I'd intended. Okay, I'll bite and include a
>similar level of detail in the solution... but the main
>point of this GotW was intended to be what I'll explain
>under Question #3.
>
>
>>JG Question
>>-----------
>>
>>1. Describe what the following code does:
>>
>> // Example 1
>> //
>> f( a++ );
>>
>> Be as complete as you can about all possibilities.
>
>A comprehensive list would be daunting, but here are
>the main possibilities.
>
>First, f could be any of the following:
>
>1. A macro. In this case the statement could mean
> just about anything, and "a++" could be evaluated
> many times or not at all. For example:
>
> #define f(x) x // once
> #define f(x) (x,x,x,x,x,x,x,x,x) // 9 times
> #define f(x) // not at all
>
> GUIDELINE: Avoid using macros. They usually make
> code more difficult to understand, and
> therefore more troublesome to maintain.
>
>2. A function. In this case, first "a++" is evaluated
> and then the result is passed to the function as its
> parameter. Normally, postincrement returns the
> previous value of a in the form of a temporary
> object, so f() could take its parameter either by
> value or by reference to const, but not by reference
> to non-const because a reference to non-const cannot
> be bound to a temporary object.
>
>3. An object. In this case, f() would be a functor,
> that is, an object for which operator()() is
> defined. Again, if postincrement returns the
> previous value of a (as postincrement always should)
> then f's operator()() could take its parameter
> either by value or by reference to const.
>
>4. A type name. In this case, the statement first
> evaluates "a++" and uses the result of that
> expression to initialize a temporary object of type
> f.
>
>Next, a could be:
>
>1. A macro. In this case, again, a could mean just
> about anything.
>
>2. An object (possibly of a built-in type). In this
> case, it must be a type for which a suitable
> operator++(int) postincrement operator is defined.
>
> Normally, postincrement should be implemented in
> terms of preincrement and should return the previous
> value of a:
>
> // Canonical form of postincrement:
> T T::operator++(int)
> {
> T old( *this ); // remember our original value
> ++*this; // always implement postincrement
> // in terms of preincrement
> return old; // return our original value
> }
>
> When you overload an operator, of course, you do
> have the option of changing its normal semantics to
> do "something unusual." For example, the following
> is likely to break the Example 1 code for most kinds
> of f, assuming a is of type A:
>
> void A::operator++(int) // doesn't return anything
>
> Don't do that. Instead, follow this sound advice:
>
> GUIDELINE: Always preserve natural semantics for
> overloaded operators. "Do as the ints
> do," that is, follow the semantics of
> the builtin types.
>
>3. A value, such as an address. For example, a could be
> the name of an array, or it could be a pointer.
>
>
>>Guru Questions
>>--------------
>
>For the remaining questions, I will make the
>simplifying assumptions that:
>
>- f() is not a macro; and
>
>- a is an object with natural postincrement semantics.
>
>
>>2. What is the difference, if any, between the
>> following two code fragments?
>>
>> // Example 2(a)
>> //
>> f( a++ );
>
>This performs the steps:
> 1. a++: Increment a and return the old value.
> 2. f(): Execute f(), passing it the old value of a.
>
>Example 2(a) ensures that the postincrement is
>performed, and therefore a gets its new value, before
>f() executes. As noted above, f() could still be a
>function, a functor, or a type name which leads to a
>constructor call.
>
>Some coding standards state that operations like ++
>should always appear on separate lines, on the grounds
>that it can be dangerous to perform multiple operations
>like ++ in the same statement because of sequence
>points (more about this in GotW #56). Instead, such
>coding standards would recommend:
>
>> // Example 2(b)
>> //
>> f( a );
>> a++;
>
>This performs the steps:
> 1. f(): Execute f(), passing it the old value of a.
> 2. a++: Increment a and return the old value, which
> is then ignored.
>
>In both cases, f() gets the old value of a. "So what's
>the big difference?" you may ask. Well, Example 2(b)
>will not always have the same effect as that in Example
>2(a), because Example 2(b) ensures that the
>postincrement is performed, and therefore a gets its
>new value, after f() executes. "But so what?" you may
>insist still, now somewhat exasperated. "What's the
>difference? What are you on about here?"
>
>Clearly, if f() and a.operator++(int) have visible side
>effects, the order in which they are executed can
>matter. But, more specifically, consider what happens
>if f() has a side effect that affects the state of a
>itself: That's neither farfetched nor unlikely, and it
>can happen even if f() doesn't and can't directly
>change a, as I'll illustrate with an example:
>
>
>>3. In Question #2, make the simplifying assumption that
>> f() is a function that takes its argument by value.
>> Now what is the difference, if any, between Example
>> 2(a) and Example 2(b)?
>
>The difference is that, for perfectly normal C++ code,
>Example 2(a) can be legal when Example 2(b) is not.
>Specifically, consider what happens when we replace f
>with list::erase(), and a with list::iterator. Now the
>first form is valid:
>
> // Example 3(a)
> //
> // l is a list<int>
> // i is a valid non-end iterator into l
>
> l.erase( i++ ); // OK
>
>But the second form is not:
>
> // Example 3(b)
> //
> // l is a list<int>
> // i is a valid non-end iterator into l
>
> l.erase( i );
> i++; // error, i is not a valid iterator
>
>The reason that Example 3(b) is incorrect is that the
>call to "l.erase( i )" invalidates i, and therefore
>you can no longer call operator++ on i afterwards.
>
>
>Scissors, Traffic, and Iterators
>--------------------------------
>
>Warning: Some programmers routinely write code like
>Example 3(b), perhaps because of coding guidelines that
>have a blanket policy of discouraging operations like
>++ in function call statements.
>
>If you're one of the programmers who writes code like
>Example 3(b), you may even be routinely getting away
>with it (and not realizing the danger) just because it
>happens to work on the current version of your compiler
>and library. But be warned: Code like Example 3(b) is
>not portable, it is not sanctioned by the Standard, and
>it's likely to turn and bite you when you port to
>another compiler platform or even just upgrade the one
>you're working on today. When it does bite, it will
>bite hard, because "using-an-invalid-iterator" bugs can
>be very difficult to find (unless you have the joy of
>working with a good checked library implementation
>during debugging -- but if you're in this situation you
>must not be using a checked implementation or else it
>would already have warned you about this!).
>
>Some mothers (who are also software engineers) give the
>following three pieces of good advice, and we should
>always strive to follow them for our own good:
>
>1. Don't run with scissors.
>
>2. Don't play in traffic.
>
>3. Don't use invalid iterators.
>
>Next time: With the exception of examples like the
>above, we'll see why it's still a good idea in general
>to avoid writing operations like ++ in function calls.