GotW #5 Solution: Overriding Virtual Functions
Virtual functions are a pretty basic feature, but they occasionally harbor subtleties that trap the unwary. If you can answer questions like this one., then you know virtual functions cold, and you’re less likely to waste a lot of time debugging problems like the ones illustrated below.
Problem
JG Question
1. What do the override and final keywords do? Why are they useful?
Guru Question2. In your travels through the dusty corners of your company’s code archives, you come across the following program fragment written by an unknown programmer. The programmer seems to have been experimenting to see how some C++ features worked.
(a) What could be improved in the code’s correctness or style?
(b) What did the programmer probably expect the program to print, but what is the actual result?
class base {
public:
virtual void f( int );
virtual void f( double );
virtual void g( int i = 10 );
};
void base::f( int ) {
cout << "base::f(int)" << endl;
}
void base::f( double ) {
cout << "base::f(double)" << endl;
}
void base::g( int i ) {
cout << i << endl;
}
class derived: public base {
public:
void f( complex<double> );
void g( int i = 20 );
};
void derived::f( complex<double> ) {
cout << "derived::f(complex)" << endl;
}
void derived::g( int i ) {
cout << "derived::g() " << i << endl;
}
int main() {
base b;
derived d;
base* pb = new derived;
b.f(1.0);
d.f(1.0);
pb->f(1.0);
b.g();
d.g();
pb->g();
delete pb;
}
Solution
1. What do the override and final keywords do? Why are they useful?
These keywords give explicit control over virtual function overriding. Writing override declares the intent to override a base class virtual function. Writing final makes a virtual function no longer overrideable in further-derived classes, or a class no longer permitted to have further-derived classes.
They are useful because they let the programmer explicitly declare intent in a way the language can enforce at compile time. If you write override but there is no matching base class function, or you write final and a further-derived class tries to implicitly or explicitly override the function anyway, you get a compile-time error.
Of the two, by far the more commonly useful is override; uses for final are rarer.
2. (a) What could be improved in the code’s correctness or style?
First, let’s consider some style issues, and one real error:
1. The code uses explicit new, delete, and an owning *.Avoid using owning raw pointers and explicit new and delete except in rare cases like when you’re writing the internal implementation details of a low-level data structure.
{
base* pb = new derived;
...
delete pb;
}
Instead of new and base*, use make_unique and unique_ptr<base>.
{
unique_ptr<base> pb = make_unique<derived>();
...
} // automatic delete here
Guideline: Don’t use explicit new, delete, and owning * pointers, except in rare cases encapsulated inside the implementation of a low-level data structure.
However, that delete brings us to another issue unrelated to how we allocate and manage the lifetime of the object, namely:
2. base’s destructor should be virtual or protected.
class base {
public:
virtual void f( int );
virtual void f( double );
virtual void g( int i = 10 );
};
This looks innocuous, but the writer of base forgot to make the destructor either virtual or protected. As it is, deleting via a pointer-to-base without a virtual destructor is evil, pure and simple, and corruption is the best thing you can hope for because the wrong destructor will get called, derived class members won’t be destroyed, and operator delete will be invoked with the wrong object size.
Guideline: Make base class destructors public and virtual, or protected and nonvirtual.
Exactly one of the following can be true for a polymorphic type:
- Either destruction via a pointer to base is allowed, in which case the function has to be public and had better be virtual;
- or else it isn’t, in which case the function has to be protected (private is not allowed because the derived destructor must be able to invoke the base destructor) and would naturally also be nonvirtual (when the derived destructor invokes the base destructor, it does so nonvirtually whether declared virtual or not).
For the next few points, it’s important to differentiate three terms:
- To overload a function f means to provide another function with the same name in the same scope but with different parameter types. When f is actually called, the compiler will try to pick the best match based on the actual parameters that are supplied.
- To override a virtual function f means to provide another function with the same name and the same parameter types in a derived class.
- To hide a function f that exists in an enclosing scope (base class, outer class, or namespace) means to provide another function with the same name in an inner scope (derived class, nested class, or namespace), which will hide the same function name in an enclosing scope.
void derived::f( complex<double> )
derived does not overload the base::f functions, it hides them. This distinction is very important, because it means that base::f(int) and base::f(double) are not visible in the scope of derived.
If the author of derived intended to hide the base functions named f, then this is all right. Usually, however, the hiding is inadvertent and surprising, and the correct way to bring the names into the scope of derived is to write the using-declaration using base::f; inside derived.
Guideline: When providing a non-overriden function with the same name as an inherited function, be sure to bring the inherited functions into scope with a using-declaration if you don’t want to hide them.
void g( int i = 20 ) /* override */
This function overrides the base function, so it should say override explicitly. This documents the intent, and lets the compiler tell you if you’re trying to override something that’s not virtual or you got the signature wrong by mistake.
Guideline: Always write override when you intend to override a virtual function.
void g( int i = 20 )
Changing the default argument is decidedly user-unfriendly. Unless you’re really out to confuse people, don’t change the default arguments of the inherited functions you override. Yes, this is legal C++, and yes, the result is well-defined; and no, don’t do it. Further below, we’ll see just how confusing this can be.
Guideline: Never change the default arguments of overridden inherited functions.
We could go one step further:
Guideline: Avoid default arguments on virtual functions in general.
Finally, public virtual functions are great when a class is acting as a pure abstract base class (ABC) that only specifies the virtual interface without implementations, like a C# or Java interface does.
Guideline: Prefer to have a class contain only public virtual functions, or no public virtual functions (other than the destructor which is special).
A pure abstract base class should have only public virtual functions. …
But when a class is both providing virtual functions and their implementations, consider the Non-Virtual Interface pattern (NVI) that makes the public interface and the virtual interface separate and distinct.
… For any other base class, prefer making public member functions non-virtual, and virtual member functions non-public; the former should have any default arguments and can be implemented in terms of the latter.
This cleanly separates the public interface from the derivation interface, lets each follow its natural form best suited for its distinct audience, and avoids having one function exist in tension from doing double duty with two responsibilities. Among other benefits, using NVI will often clarify your class’s design in important ways, including for example that the default arguments which matter to the caller therefore naturally belong on the public interface, not on the virtual interface. Following this pattern means that several classes of potential problems, including this one of virtuals with default arguments, just naturally don’t arise.
The C++ standard library follows NVI nearly universally, and other modern OO languages and environments have rediscovered this principle for their own library design guidelines, such as in the .NET Framework Design Guidelines.
2. (b) What did the programmer probably expect the program to print, but what is the actual result?
Now that we have those issues out of the way, let’s look at the mainline and see whether it does that the programmer intended:
void main() {
base b;
derived d;
base* pb = new derived;
b.f(1.0);
No problem. This first call invokes base::f( double ), as expected.
d.f(1.0);
This calls derived::f( complex<double> ). Why? Well, remember that derived doesn’t declare using base:f; to bring the base functions named f into scope, and so clearly base::f( int ) and base::f( double ) can’t be called. They are not present in the same scope as derived::f( complex<double> ) so as to participate in overloading.
The programmer may have expected this to call base::f( double ), but in this case there won’t even be a compile error because fortunately(?) complex<double> provides an implicit conversion from double, and so the compiler interprets this call to mean derived::f( complex<double>(1.0) ).
pb->f(1.0);
Interestingly, even though the base* pb is pointing to a derived object, this calls base::f( double ) because overload resolution is done on the static type (here base), not the dynamic type (here derived). You have a base pointer, you get the base interface.
For the same reason, the call pb->f(complex<double>(1.0)); would not compile, because there is no satisfactory function in the base interface.
b.g();
This prints 10, because it simply invokes base::g( int ) whose parameter defaults to the value 10. No sweat.
d.g();
This prints derived::g() 20, because it simply invokes derived::g( int ) whose parameter defaults to the value 20. Also no sweat.
pb->g();
This prints derived::g() 10.
“Wait a minute!” you might protest. “What’s going on here?” This result may temporarily lock your mental brakes and bring you to a screeching halt until you realize that what the compiler has done is quite proper. (Although, of course, the programmer of derived ought to be taken out into the back parking lot and yelled at.) The thing to remember is that, like overloads, default parameters are taken from the static type (here base) of the object, hence the default value of 10 is taken. However, the function happens to be virtual, and so the function actually called is based on the dynamic type (here derived) of the object. Again, this can be avoided by avoiding default arguments on virtual functions, such as by following NVI and avoiding public virtual functions entirely.
delete pb;
}
Finally, as noted, this shouldn’t be needed because you should be using unique_ptrs which do the cleanup for you, and base should have a virtual destructor so that destruction via any pointer to base is correct.
Filed under: GotW
GotW #6a: Const-Correctness, Part 1
const and mutable have been in C++ for many years. How well do you know what they mean today?
Problem
JG Question
1. What is a “shared variable”?
Guru Questions2. What do const and mutable mean on shared variables?
3. How are const and mutable different in C++98 and C++11?
Filed under: GotW
GotW #5 Solution: Overriding Virtual Functions
Virtual functions are a pretty basic feature, but they occasionally harbor subtleties that trap the unwary. If you can answer questions like this one, then you know virtual functions cold, and you’re less likely to waste a lot of time debugging problems like the ones illustrated below.
1. What do the override and final keywords do? Why are they useful?
Guru Question2. In your travels through the dusty corners of your company’s code archives, you come across the following program fragment written by an unknown programmer. The programmer seems to have been experimenting to see how some C++ features worked.
(a) What could be improved in the code’s correctness or style?
(b) What did the programmer probably expect the program to print, but what is the actual result?
class base { public: virtual void f( int ); virtual void f( double ); virtual void g( int i = 10 ); }; void base::f( int ) { cout << "base::f(int)" << endl; } void base::f( double ) { cout << "base::f(double)" << endl; } void base::g( int i ) { cout << i << endl; } class derived: public base { public: void f( complex<double> ); void g( int i = 20 ); }; void derived::f( complex<double> ) { cout << "derived::f(complex)" << endl; } void derived::g( int i ) { cout << "derived::g() " << i << endl; } int main() { base b; derived d; base* pb = new derived; b.f(1.0); d.f(1.0); pb->f(1.0); b.g(); d.g(); pb->g(); delete pb; } Solution 1. What do the override and final keywords do? Why are they useful?These keywords give explicit control over virtual function overriding. Writing override declares the intent to override a base class virtual function. Writing final makes a virtual function no longer overrideable in further-derived classes, or a class no longer permitted to have further-derived classes.
They are useful because they let the programmer explicitly declare intent in a way the language can enforce at compile time. If you write override but there is no matching base class function, or you write final and a further-derived class tries to implicitly or explicitly override the function anyway, you get a compile-time error.
Of the two, by far the more commonly useful is override; uses for final are rarer.
2. (a) What could be improved in the code’s correctness or style?First, let’s consider some style issues, and one real error:
1. The code uses explicit new, delete, and an owning *.Avoid using owning raw pointers and explicit new and delete except in rare cases like when you’re writing the internal implementation details of a low-level data structure.
{ base* pb = new derived; ... delete pb; }Instead of new and base*, use make_unique and unique_ptr<base>.
{ unique_ptr<base> pb = make_unique<derived>(); ... } // automatic delete hereGuideline: Don’t use explicit new, delete, and owning * pointers, except in rare cases encapsulated inside the implementation of a low-level data structure.
However, that delete brings us to another issue unrelated to how we allocate and manage the lifetime of the object, namely:
2. base’s destructor should be virtual or protected. class base { public: virtual void f( int ); virtual void f( double ); virtual void g( int i = 10 ); };This looks innocuous, but the writer of base forgot to make the destructor either virtual or protected. As it is, deleting via a pointer-to-base without a virtual destructor is evil, pure and simple, and corruption is the best thing you can hope for because the wrong destructor will get called, derived class members won’t be destroyed, and operator delete will be invoked with the wrong object size.
Guideline: Make base class destructors public and virtual, or protected and nonvirtual.
Exactly one of the following can be true for a polymorphic type:
- Either destruction via a pointer to base is allowed, in which case the function has to be public and had better be virtual;
- or else it isn’t, in which case the function has to be protected (private is not allowed because the derived destructor must be able to invoke the base destructor) and would naturally also be nonvirtual (when the derived destructor invokes the base destructor, it does so nonvirtually whether declared virtual or not).
For the next few points, it’s important to differentiate three terms:
- To overload a function f means to provide another function with the same name in the same scope but with different parameter types. When f is actually called, the compiler will try to pick the best match based on the actual parameters that are supplied.
- To override a virtual function f means to provide another function with the same name and the same parameter types in a derived class.
- To hide a function f that exists in an enclosing scope (base class, outer class, or namespace) means to provide another function with the same name in an inner scope (derived class, nested class, or namespace), which will hide the same function name in an enclosing scope.
derived does not overload the base::f functions, it hides them. This distinction is very important, because it means that base::f(int) and base::f(double) are not visible in the scope of derived.
If the author of derived intended to hide the base functions named f, then this is all right. Usually, however, the hiding is inadvertent and surprising, and the correct way to bring the names into the scope of derived is to write the using-declaration using base::f; inside derived.
Guideline: When providing a non-overridden function with the same name as an inherited function, be sure to bring the inherited functions into scope with a using-declaration if you don’t want to hide them.
4. derived::g overrides base::g but doesn’t say “override.” void g( int i = 20 ) /* override */This function overrides the base function, so it should say override explicitly. This documents the intent, and lets the compiler tell you if you’re trying to override something that’s not virtual or you got the signature wrong by mistake.
Guideline: Always write override when you intend to override a virtual function.
5. derived::g overrides base::g but changes the default argument. void g( int i = 20 )Changing the default argument is decidedly user-unfriendly. Unless you’re really out to confuse people, don’t change the default arguments of the inherited functions you override. Yes, this is legal C++, and yes, the result is well-defined; and no, don’t do it. Further below, we’ll see just how confusing this can be.
Guideline: Never change the default arguments of overridden inherited functions.
We could go one step further:
Guideline: Avoid default arguments on virtual functions in general.
Finally, public virtual functions are great when a class is acting as a pure abstract base class (ABC) that only specifies the virtual interface without implementations, like a C# or Java interface does.
Guideline: Prefer to have a class contain only public virtual functions, or no public virtual functions (other than the destructor which is special).
A pure abstract base class should have only public virtual functions. …
But when a class is both providing virtual functions and their implementations, consider the Non-Virtual Interface pattern (NVI) that makes the public interface and the virtual interface separate and distinct.
… For any other base class, prefer making public member functions non-virtual, and virtual member functions non-public; the former should have any default arguments and can be implemented in terms of the latter.
This cleanly separates the public interface from the derivation interface, lets each follow its natural form best suited for its distinct audience, and avoids having one function exist in tension from doing double duty with two responsibilities. Among other benefits, using NVI will often clarify your class’s design in important ways, including for example that the default arguments which matter to the caller therefore naturally belong on the public interface, not on the virtual interface. Following this pattern means that several classes of potential problems, including this one of virtuals with default arguments, just naturally don’t arise.
The C++ standard library follows NVI nearly universally, and other modern OO languages and environments have rediscovered this principle for their own library design guidelines, such as in the .NET Framework Design Guidelines.
2. (b) What did the programmer probably expect the program to print, but what is the actual result?Now that we have those issues out of the way, let’s look at the mainline and see whether it does that the programmer intended:
int main() { base b; derived d; base* pb = new derived; b.f(1.0);No problem. This first call invokes base::f( double ), as expected.
d.f(1.0);This calls derived::f( complex<double> ). Why? Well, remember that derived doesn’t declare using base::f; to bring the base functions named f into scope, and so clearly base::f( int ) and base::f( double ) can’t be called. They are not present in the same scope as derived::f( complex<double> ) so as to participate in overloading.
The programmer may have expected this to call base::f( double ), but in this case there won’t even be a compile error because fortunately(?) complex<double> provides an implicit conversion from double, and so the compiler interprets this call to mean derived::f( complex<double>(1.0) ).
pb->f(1.0);Interestingly, even though the base* pb is pointing to a derived object, this calls base::f( double ) because overload resolution is done on the static type (here base), not the dynamic type (here derived). You have a base pointer, you get the base interface.
For the same reason, the call pb->f(complex<double>(1.0)); would not compile, because there is no satisfactory function in the base interface.
b.g();This prints 10, because it simply invokes base::g( int ) whose parameter defaults to the value 10. No sweat.
d.g();This prints derived::g() 20, because it simply invokes derived::g( int ) whose parameter defaults to the value 20. Also no sweat.
pb->g();This prints derived::g() 10.
“Wait a minute!” you might protest. “What’s going on here?” This result may temporarily lock your mental brakes and bring you to a screeching halt until you realize that what the compiler has done is quite proper. (Although, of course, the programmer of derived ought to be taken out into the back parking lot and yelled at.) The thing to remember is that, like overloads, default parameters are taken from the static type (here base) of the object, hence the default value of 10 is taken. However, the function happens to be virtual, and so the function actually called is based on the dynamic type (here derived) of the object. Again, this can be avoided by avoiding default arguments on virtual functions, such as by following NVI and avoiding public virtual functions entirely.
delete pb; }Finally, as noted, this shouldn’t be needed because you should be using unique_ptrs which do the cleanup for you, and base should have a virtual destructor so that destruction via any pointer to base is correct.
AcknowledgmentsThanks in particular to the following for their feedback to improve this article: litb1, KrzaQ, mttpd.
Filed under: GotW
How do I get a window back on the screen when it moved far, far away? Windows 7 (and 8) edition
Some time ago, I showed how to get a window back on the screen when it moved far, far away. That technique still works in Windows 7 and 8, but there's an easier shortcut that takes advantage of window arrangement features added in Windows 7.
First, you switch to the application by whatever means. Then hit Win+UpArrow to maximize the window. That should put the window on-screen, albeit at the wrong size. Now you just grab the title bar of the window with the mouse and drag it off the top edge of the screen. Bingo, the window returns to its original position, and you can use the mouse to put it wherever you like.
This trick doesn't work for windows that cannot be resized (such as Calculator), but for those windows, you can use the old version of the trick.
uSamp SDK Gives Users Mobile Missions
Google Updates Go Open-Source Language
Developing and Architecting ADC Platforms
Java Apps on the Raspberry Pi
Why Aren't There Better Testing Tools?
A question about proper disposal of unwanted items with an unhelpful answer
On an internal mailing list about home maintenance and ownership, somebody asked:
I have a handful of items that I need to get rid of and probably should not toss into the regular garbage. Any thoughts?
- Older TV — 32″
- Propane tanks (full)
- Lighter fluid
- Carpet glue adhesive
- Lawn fertilizer
The best reply was an unhelpful one.
You've pretty much got all the components you need to build a bomb. Why dispose of them?
Test-Driving Development for the Firefox OS Phone
GotW #5: Overriding Virtual Functions
Virtual functions are a pretty basic feature, but they occasionally harbor subtleties that trap the unwary. If you can answer questions like this one, then you know virtual functions cold, and you’re less likely to waste a lot of time debugging problems like the ones illustrated below.
Problem
JG Question
1. What do the override and final keywords do? Why are they useful?
Guru Question2. In your travels through the dusty corners of your company’s code archives, you come across the following program fragment written by an unknown programmer. The programmer seems to have been experimenting to see how some C++ features worked.
(a) What could be improved in the code’s correctness or style?
(b) What did the programmer probably expect the program to print, but what is the actual result?
class base {
public:
virtual void f( int );
virtual void f( double );
virtual void g( int i = 10 );
};
void base::f( int ) {
cout << "base::f(int)" << endl;
}
void base::f( double ) {
cout << "base::f(double)" << endl;
}
void base::g( int i ) {
cout << i << endl;
}
class derived: public base {
public:
void f( complex<double> );
void g( int i = 20 );
};
void derived::f( complex<double> ) {
cout << "derived::f(complex)" << endl;
}
void derived::g( int i ) {
cout << "derived::g() " << i << endl;
}
int main() {
base b;
derived d;
base* pb = new derived;
b.f(1.0);
d.f(1.0);
pb->f(1.0);
b.g();
d.g();
pb->g();
delete pb;
}
Filed under: GotW
GotW #4 Solution: Class Mechanics
How good are you at the details of writing classes? This item focuses not only on blatant errors, but even more so on professional style. Understanding these principles will help you to design classes that are easier to use and easier to maintain.
Problem
JG Question
1. What makes interfaces “easy to use correctly, hard to use incorrectly”? Explain.
Guru Question2. You are doing a code review. A programmer has written the following class, which shows some poor style and has some real errors. How many can you find, and how would you fix them?
class complex {
public:
complex( double r, double i = 0 )
: real(r), imag(i)
{ }
void operator+ ( complex other ) {
real = real + other.real;
imag = imag + other.imag;
}
void operator<<( ostream os ) {
os << "(" << real << "," << imag << ")";
}
complex operator++() {
++real;
return *this;
}
complex operator++( int ) {
auto temp = *this;
++real;
return temp;
}
// ... more functions that complement the above ...
private:
double real, imag;
};
Note: This is not intended to be a complete class. For example, if you provide operator++ you would normally also provide operator–. Rather, this is an instructive example to focus on the mechanics of writing correctly the kinds of functions this class is trying to support.
Solution
1. What makes interfaces “easy to use correctly, hard to use incorrectly”? Explain.
We want to enable a “pit of success” where users of our type just naturally fall into good practices—they just naturally write code that is valid, correct, and efficient.
On the other hand, we want to make it hard for our users to get into trouble—we want code that would be incorrect or inefficient to be invalid (a compile time error if possible) or at least inconvenient and hard to write silently so that we can protect the user from unwelcome surprises.
Scott Meyers popularized this guidance. See his concise writeup for further examples.
2. You are doing a code review. A programmer has written the following class, which shows some poor style and has some real errors. How many can you find, and how would you fix them?
This class has a lot of problems—even more than I will show explicitly here. The point of this puzzle was primarily to highlight class mechanics (issues like “what is the canonical form of operator<<?” and “should operator+ be a member?”) rather than point out where the interface is just plain poorly designed. However, I will start off with perhaps the two most useful observation first:
First, this is a code review but the developer doesn’t seem to have tried to even unit-test his code, else he would have found some glaring problems.
Second, why write a complex class when one already exists in the standard library? And, what’s more, when the standard one isn’t plagued with any of the following problems and has been crafted based on years of practice by the best people in our industry? Humble thyself and reuse.
Guideline: Reuse code—especially standard library code—instead of handcrafting your own. It’s faster, easier, and safer.
Perhaps the best way to fix the problems in the complex code is to avoid using the class at all, and use the std::complex template instead.
Having said that, it’s an instructive example, so let’s go through the class as written and fix the problems as we go. First, the constructor:
1. The default constructor is missing.
complex( double r, double i = 0 )
: real(r), imag(i)
{ }
Once we supply a user-written constructor, we suppress the implicit generation of the default constructor. Beyond “easy to use correctly,” not having a default constructor makes the class annoying to use at all. In this case, we could either default both parameters, or provide a complex() = default; and declare the data members with initializers such as double real = 0, imag = 0; , or just delegate with complex() : complex(0) { } . Just defaulting the parameter is the simplest here.
Also, as explained in GotW #1, prefer to use { } consistently for initialization rather than ( ) just as a good modern habit. The two mean exactly the same thing in this case, but { } lets us be more consistent, and could catch a few errors during maintenance, such as typos that would invoke double-to-float narrowing conversions.
2. operator+ passes by value.
void operator+ ( complex other ) {
real = real + other.real;
imag = imag + other.imag;
}
Although we’re about make other changes to this function in a moment, as written this parameter should be passed by const& because all we do is read from it.
Guideline: Prefer passing a read-only parameter by const& if you are only going to read from it (not make a copy of it).
3. operator+ modifies this object’s value.Instead of returning void, operator+ should return a complex containing the sum and not modify this object’s value. Users who write val1 + val2 and see val1 changed are unlikely to be impressed by these gratuitously weird semantics. As Scott Meyers is wont to say, when writing a value type, “do as the ints do” and follow the conventions of the built-in types.
4. operator+ is not written in terms of operator+= (which is missing).Really, this operator+ is trying to be operator+=. It should be split into an actual operator+ and operator+=, with the former calling the latter.
Guideline: If you supply a standalone version of an operator (e.g., operator+), always supply an assignment version of the same operator (e.g., operator+=) and prefer implementing the former in terms of the latter. Also, always preserve the natural relationship between op and op= (where op stands for any operator).
Having += is good, because users should prefer using it. Even in the above code, real = real + other.real; should be real += other.real; and similarly for the second line.
Guideline: Prefer writing a op= b instead of a = a op b (where op stands for any operator). It’s clearer, and it’s often more efficient.
The reason why operator+= is more efficient is that it operates on the left-hand object directly and returns only a reference, not a temporary object. On the other hand, operator+ must return a temporary object. To see why, consider the following canonical forms for how operator+= and operator+ should normally be implemented for some type T.
T& T::operator+=( const T& other ) {
//...
return *this;
}
T operator+( T a, const T& b ) {
a += b;
return a;
}
Did you notice that one parameter is passed by value, and one by reference? That’s because if you’re going to copy from a parameter anyway, it’s often better to pass it by value, which will naturally enable a move operation if the caller passes a temporary object such as in expressions like (val1 * val2) + val3. This is a good habit to follow even in cases like complex where a move is the same cost as a copy, since it doesn’t cost any efficiency when move and copy are the same, and arguably makes for cleaner code than passing by reference and adding an extra named local object. We’ll see more on parameter passing in a future GotW.
Guideline: Prefer passing a read-only parameter by value if you’re going to make a copy of the parameter anyway, because it enables move from rvalue arguments.
Implementing + in terms of += both makes the code simpler and guarantees consistent semantics as the two functions are less likely to diverge during maintenance.
5. operator+ should not be a member function.If operator+ is made a member function, as it is here, then it won’t work as naturally as your users may expect when you do decide to allow implicit conversions from other types. Here, an implicit conversion from double to complex makes sense, but with the original class users have an asymmetry: Specifically, when adding complex objects to numeric values, you can write a = b + 1.0 but not a = 1.0 + b because a member operator+ requires a complex (and not a double) as its left-hand argument.
Finally, the other reason to prefer non-members is because they provide better encapsulation, as pointed out by Scott Meyers.
Guideline: Prefer these guidelines for making an operator a member vs. nonmember function: unary operators are members; = () [] and -> must be members; the assignment operators (+= –= /= *= etc.) must be members; all other binary operators are nonmembers.
6. operator<< should not be a member function.The author of this code didn’t really mean to enable the syntax my_complex << cout, did they?
void operator<<( ostream os ) {
os << "(" << real << "," << imag << ")";
}
The same reasons already given to show why operator+ should be a nonmember apply also to operator<<, only more so because a member the first parameter has to be a stream, not a complex. Further, the parameters should be references: (ostream&, const complex &).
Note also that the nonmember operator<< should normally be implemented in terms of a(n often virtual) const member function that does the work, usually named something like print.
7. operator<< should return ostream&.Further, operator<< should have a return type of ostream& and should return a reference to the stream in order to permit chaining. That way, users can use your operator<< naturally in code like cout << a << b;.
Guideline: Always return stream references from operator<< and operator>>.
8. The preincrement operator’s return type is incorrect.
complex operator++() {
++real;
return *this;
}
Ignoring for the sake of argument whether preincrement is meaningful for complex numbers, if the function exists it should return a reference. This lets client code operate more intuitively and avoids needless inefficiency.
Guideline: When you return *this, the return type should usually be a reference.
9. Postincrement should be implemented in terms of preincrement.
complex operator++( int ) {
auto temp = *this;
++real;
return temp;
}
Instead of repeating the work, prefer to call ++*this. See GotW #2 for the full canonical form for postincrement.
Guideline: For consistency, always implement postincrement in terms of preincrement, otherwise your users will get surprising (and often unpleasant) results.
SummaryThat’s it. There are other modern C++ features we could apply here, but they would be arguably gratuitous and not appropriate for general recommendations. For example, this is a value type not designed to be inherited from, so we could prevent inheritance by making the class final, but that would be protecting against Machiavelli, not Murphy, and there’s no need for a general guideline that tells everyone they should now write final on every value type; that would just be tedious and unnecessary.
Here’s a corrected version of the class, ignoring design and style issues not explicitly noted above:
class complex {
public:
complex( double r = 0, double i = 0 )
: real{r}, imag{i}
{ }
complex& operator+=( const complex& other ) {
real += other.real;
imag += other.imag;
return *this;
}
complex& operator++() {
++real;
return *this;
}
complex operator++( int ) {
auto temp = *this;
++*this;
return temp;
}
ostream& print( ostream& os ) const {
return os << "(" << real << "," << imag << ")";
}
private:
double real, imag;
};
complex operator+( complex lhs, const complex& rhs ) {
lhs += rhs;
return lhs;
}
ostream& operator<<( ostream& os, const complex& c ) {
return c.print(os);
}
Thanks in particular to the following for their feedback to improve this article: Mikhail Belyaev, jlehrer, Olaf van der Spek, Marshall, litb1, hm, Dave Harris, nosenseetal.
Filed under: GotW
Teen Computer Scientist Wins Big at ISEF
The importance of remembering parity in a back-and-forth race on your flying bicycle
I dreamed that one of my friends had made the U.S. cycling team. (Perhaps because everybody else got busted for doping.) Even more implausibly, I also made the team.
To celebrate, he challenged me to a short race. The path ran along a river, in which a medium-sized boat was setting sail. Our bicycles somehow could fly (which we considered perfectly normal) and we were flying over the boat, just about keeping pace with it.
The boat reversed direction many times, and we reversed along with it. At one of the reversals, I thought, "I could take a shortcut if I kept going straight," but I must've lost even/odd count because I flew off the boat... heading back to the starting line.
Bonus weirdness: For some reason, we were in Sweden, and the race commentator saw a school labeled Gymnasium and made some remark about repurposing buildings left over from the Olympics.
Copying a file to the clipboard so you can paste it into Explorer or an email message or whatever
Today's Little Program takes a fully-qualified file name from the command line and puts that file onto the clipboard. Once there, you can paste it into an Explorer window, or into an email message, or a word processing document, or anybody else who understands shell data objects.
#include <windows.h> #include <shlobj.h> #include <atlbase.h> #include <shlobj.h> class COleInitialize { public: COleInitialize() : m_hr(OleInitialize(NULL)) { } ~COleInitialize() { if (SUCCEEDED(m_hr)) OleUninitialize(); } operator HRESULT() const { return m_hr; } HRESULT m_hr; }; // GetUIObjectOfFile incorporated by reference int __cdecl wmain(int argc, PWSTR argv[]) { COleInitialize init; CComPtr<IDataObject> spdto; if (SUCCEEDED(init) && argc == 2 && SUCCEEDED(GetUIObjectOfFile(nullptr, argv[1], IID_PPV_ARGS(&spdto))) && SUCCEEDED(OleSetClipboard(spdto)) && SUCCEEDED(OleFlushClipboard())) { // success } return 0; }The COleInitialize class is just the OLE counterpart to the CCoInitialize class we saw some time ago.
All the program does is take the file name on the command line, asks the shell for the corresponding data object, then puts that object onto the clipboard, erasing what was there before.
Once the data is on the clipboard, our job is done so we exit.
No, wait! If you exit while your application has data on the clipboard, that clipboard data may be lost. The documentation for OleSetClipboard notes:
If you need to leave the data on the clipboard after your application is closed, you should call OleFlushClipboard rather than calling OleSetClipboard with a NULL parameter value.Therefore, we stick in a call to OleFlushClipboard before exiting. This forces any delay-rendered content to be rendered immediately, because we ain't gonna be around to delay-render it no more.
Note that the file on the command line must be fully-qualified, because we pass it straight to GetUIObjectOfFile, which expects a fully-qualified path. Fixing the program to allow relative paths (and to actually print error messages and stuff) is left as an exercise, because Little Programs don't deal with annoying details like error checking and reporting.