To default, or not to default, that is the question...
One of the "interesting" features of C++ is the ability to default the value of parameters to a method.
It's one of those features that I'm sure that Bjarne Stroustrup thought was just flat-out neat (there are a number of feature in C++ that fall into the broad category of "convenience features", I'm pretty sure that defaulted parameters is one of them).
One of the developers (Nick) in my group was working on a fix for a bug, the fix required that he add a new parameter to a relatively common routine.
It turned out that for all of the calls to that routine, except for one, the new parameter would have the same value.
Nick's suggestion was to add the new parameter as a defaulted parameter because that way he wouldn't have to change that much code.
Eventually I vetoed the idea, because of the following problem.
Let's say you have a function foo:
HRESULT Foo(DWORD param1, BOOL param2=FALSE)
{
:
:
}
Everything's great - your code's running, and it's all great.
What happens six months later when you need to change the signature for Foo to:
HRESULT Foo(DWORD param1, void *newParam, BOOL param2=FALSE)
{
:
:
}
Well, in this case, you're in luck, you can simply compile the code and the compiler will happily tell you every function that needs to change.
On the other hand, what if the change was:
HRESULT Foo(DWORD param1, DWORD newParam, BOOL param2=FALSE)
{
:
:
}
Now that's a horse of a different color. The problem is that the types for BOOL and DWORD are compatible. It means that any code that specified a value for param2, like:
hr = Foo(32, TRUE);
is still going to compile without error. The compiler will simply interpret it as:
hr = Foo(param1=32, newParam=1, param2=FALSE);
Now the language lawyer's are going to shout up and down that this is a design problem in Windows, the BOOL and DWORD types shouldn't have both been defined as "unsigned long", that instead param2 should have been defined as "bool".
The problem is that you STILL have problems. If param2 was defined as 'bool', what happens if you need to add a non default parameter that's of type 'bool'? You're back where you were before.
Or you could have:
HRESULT Foo(DWORD param1, int newParam, short param2=3)
{
:
:
}
In this case, the automatic promotion rules will quite happily promote a short to an int without a warning.
There have been dozens of times when I've discovered bugs that were introduced by essentially this pattern - someone added a parameter to a function that has defaulted parameters, there was an automatic conversion between the defaulted parameter and the newly added parameter, and what was a simple change all of a sudden became a bug.
Comments
- Anonymous
July 20, 2006
Well...you can remove the 'default'ing at the point in time when it DOES create a problem (ie when you are adding third parameter) rather than where it is unlikely to create a problem (ie when adding the second parameter). A quick fix - thoughtfully applied - need not be costly in the long run.
My usual formula for adding a default parameter to an existing function is: Add it as non-default parameter. Look at ALL the compiler errors, fix those that would want to use the non-default value and then revert to default parameter. - Anonymous
July 20, 2006
Named default parameters sure are nice about this, aren't they. ;) To bad they're such a hassle to write compilers for, they're so useful once you get used to them. - Anonymous
July 20, 2006
If you combine default parameter values with inheritance, there are even more potential gotchas, for example:
#include <stdio.h>
struct B {
virtual void Test(int i = 10) { printf("B::Test, i is %dn", i); }
};
struct D : public B {
virtual void Test(int i = 20) { printf("D::Test, i is %dn", i); }
};
int main(int argc, char* argv[])
{
D d;
B* pb = &d;
pb->Test();
return 0;
} - Anonymous
July 20, 2006
The comment has been removed - Anonymous
July 20, 2006
The comment has been removed - Anonymous
July 20, 2006
The comment has been removed - Anonymous
July 20, 2006
The comment has been removed - Anonymous
July 20, 2006
The comment has been removed - Anonymous
July 20, 2006
The thing is, you can't just add the arguments in any order.
I personally have nearly outlawed the default parameters in my group for this very reason. We use the forwarding techinque Barry talks about, but you can't just add the arguments anywhere. Like Larray mentioned, you can still have problems.
In Larry's example, the only reason we didn't add the new argument to the end is because of the default argument. Once you get rid of default arguments, then there is little reason to add in the middle.
There are some other problems to the forwarding method, but in general, I have had less problems with forwarding than with default arguments. - Anonymous
July 20, 2006
The comment has been removed - Anonymous
July 20, 2006
What about "STRICT" type checking in windows? are you supporting this? in any of my projects, I didnt use that :( - Anonymous
July 20, 2006
The comment has been removed - Anonymous
July 20, 2006
You only have to look at the .NET Framework to realize that default arguments are occasionally useful; it's silly to expose 30 overloads and have to manually code 27 of them as forwarders when you could have only one show up in the IDE's drop-down whose default arguments show you exactly what you are going to get by omitting arguments.
IMO, I don't believe this is a case of default argument vs. overloading -- it's a case of where the new function really should have used a different name. - Anonymous
July 20, 2006
The comment has been removed - Anonymous
July 20, 2006
The comment has been removed - Anonymous
July 20, 2006
sarathc, STRICT just makes all the various USER and GDI objects have unique handle types. It dosn't change the type of BOOL vs DWORD (as far as I know). - Anonymous
July 21, 2006
I agree with steveg and prefer the FooEx option.
It has the added benefit that if the function is exposed from a dll none of the clients will require a recompile.
I also tend to avoid the default parameters methodology for the reasons Larry describes. - Anonymous
July 21, 2006
The comment has been removed - Anonymous
July 21, 2006
Depending on the frequency of the function, this seems like a problem that could easily just be solved by just listening to the compiler errors. If there are tons of calls, then a simple regular expression would do the trick. I think Larry's totally right. - Anonymous
July 21, 2006
This is not just a problem with default argument values. It is an instance of a classic (and ancient) problem whose solution I learned long ago from my friend Bill: If you change HOW it works, then change the NAME, too. This is named above the FooEx solution. Sure, C prototypes are great and help a lot. And function overloading is a pay-less-now-vs.-more-later trap. Call me a fossil, but I'll take a solution that works in asm as well as C++. So there. - Anonymous
July 21, 2006
Oops. I meant "If you change the INTERFACE (not "how it works"), then change the NAME, too." - Anonymous
July 22, 2006
A better analysis is that adding default parameters is a short-term strategy, not a long-term one. That is, the first time you have to change the method, adding a default may work. It might even work the second time. Eventually, though, the list of defaults gets weird and ungainly.
Since Microsoft should be designing interfaces for the long run, it should not use defaulted parameters. - Anonymous
July 22, 2006
Defaultable parameters are a WONDERFUL idea.
They're just not especially compatible with conventional ordered-parameter-lists.
For languages that support named parameters[1], you get all the benefits of defaultable parameters, without this subtle bug haven.
[1] e.g., Visual Basic for Applications; TRANSACT-SQL; and to a lesser extent, Perl - Anonymous
July 24, 2006
I've had similar problems when using default arguments in a scripting environment.
For example, one of the Microsoft security patches changed one of the default arguments (that we didn't specify of course), and suddenly our scripts would no longer run on computers with the latest patches.
Of course this is to be expected with a scripting language, but who would have ever thought that they might change the "default" value in a future release! - Anonymous
July 24, 2006
This risk with default parameters can't happen in my team...
It used to be that people would create function like Func( ..., FALSE, TRUE, FALSE ) but now I have banned that.
Now, I force developers to create a descriptive enum for these FALSE/TRUE magic values. This makes sense for various reasons :
1) For code readability. I've never seen a function where it was acceptable to have more than one bool value. You can't guess that the other values are for without reading the function documentation
2) the parameter is now of a specific type, that enum type, so the compiler will block passing it just any value without having had to think about what you really wanted to pass
We still use default values. When you decide to put a non-default, you really have to think about it.
If we add a parameter at the wrong place, then the compiler will not let it pass as this blog entry describes.
If people are wondering, isn't that poluting the global namespace with tons of enums? Well, no, most of our code is COM interface, and the enums are defined inside the interface. If they're helper functions, they're in a namespace/
An ficticious example of function would be something like
CheckPathName( szPath, IFicticious::DoNotAcceptUNC );
which is a whole lot better than
CheckPathName( szPath, FALSE ); - Anonymous
July 24, 2006
Saturday, July 22, 2006 2:56 PM by Maurits
> Defaultable parameters are a WONDERFUL idea.
> For languages that support named parameters[1],
[1] JCL. Nonetheless everyone hated it. IBM's macro assembler had a syntax pretty close to that, but for some reason everyone didn't hate it.
Know any examples more than 42 years old?
Monday, July 24, 2006 9:16 PM by Ulric
> Now, I force developers to create a descriptive enum for
> these FALSE/TRUE magic values.
You mean FALSE doesn't descriptively say which direction the data are being transfered between your program's variables and the controls in the screen? It's exactly the opposite direction of TRUE, you know? And TRUE is perfectly obvious because it's the opposite direction of FALSE.
Now all we have to do is figure out which direction should be the default ^_^ - Anonymous
July 26, 2006
The comment has been removed - Anonymous
July 26, 2006
I once discovered the hard way that it's easy to make cut & paste errors when using functions with default parameters. This issue applies equally well to overloaded functions.
For example, I once had to update some code so that calls to a particular function were done conditionally, e.g. replacing:
func(pSomeClass, pSomeSymbol);
with
if (pSomeClass) {
func(pSomeClass, pSomeSymbol);
} else {
// do some other stuff
}
What I failed to notice was that one of those calls was:
func(pSomeClass, pSomeSymbol, o);
and I replaced it with:
if (pSomeClass) {
func(pSomeClass, pSomeSymbol);
} else {
// do some other stuff
}
This was missed in a code review, and passed our check-in testing. - Anonymous
July 27, 2006
The comment has been removed - Anonymous
August 18, 2006
The comment has been removed - Anonymous
September 27, 2006
> There's a C++0x proposal to make "default" a valid argument for function calls. The result is that you can add additional parameters after defaulted arguments.
If you're going to have to type "default", you might as well make it a non-default parameter and type in its actual value! - Anonymous
January 25, 2008
PingBack from http://websitescripts.247blogging.info/larry-ostermans-weblog-to-default-or-not-to-default-that-is-the/