Gripe #5: Exception Handling and operator new
In C++ you can easily change the new operator to throw an exception when it runs out of memory instead of returning NULL. Cool idea, but it can be very dangerous because this causes a global change in the behaviour of a commonly used operator. What happens when you do that in a program that contains libraries that do not have that assumption?
try
{
m_pbar = m_pfoo->bar();
} catch // ...
where you grabbed the source code for that class and just kind of compiled it in:
Bar * CFoo::bar(void)
{
HANDLE h = NULL;
h = GoGetMeAFileHandle();
m_pBlah = new CBlah;
if (NULL == m_pBlah){
FreeFileHandle(h);
return NULL;
} // ...
You didn't write CFoo::bar, but you've just broken its error handling if you redefined new to throw an error instead of returning NULL. Now if the allocation of CBlah fails, you've leaked a kernel object. This is another example where idioms like smart pointers need to be consistently applied across an application in order to reap their benefits.
I have debugged leaks in IIS where these kinds of things happen and they are absolutely no fun for our customers.
I once debugged some code where the new operator had been redefined to throw an exception, and the authors did something very clever. (Aside: clever is bad -- clever is hard to figure out! If the code is clever, rewrite it until it is brain-dead obvious.) In the debug build, they wrote some magic into the operator so that it detected if it was being called without an exception handler and raised an assertion. Sounds cool, right? Any possible negative consequences of that?
Well, how about the fact that static objects which have constructors that call new cannot be linked in to the application without the program asserting on startup? The standard map, vector, etc, objects are such objects. Their constructors call new, and therefore you can't have a static map. Statics have no context to catch the possible throw, and therefore will assert when the debug application starts up. (Guess how I found that bug?)
That said, I can see how it is useful to have a new operator that throws an exception. You know what I'd do if I wanted that? I'd define an operator overload that took an argument, create a dummy identifier called "throws", and then say pBar = new(throws) CBar -- now it is perfectly clear what the semantics of that thing are.
Redefining the global new operator works great if you have 100% control over all contexts where the operator is used. Unfortunately, that's frequently not a realistic assumption!