Joe Ganley

I make software and sometimes other things.

 

Due to Blogger's termination of support for FTP, this blog is no longer active.

It is possible that some links from here, particularly those within the site, are now broken. If you encounter one of those, your best bet is to go to the new front page and hunt for it from there.

Most, but not all, of the blog's posts are on this page; the archives are here.

 

I ran into a bug the other day that I've run into enough times in my career that I thought it was worth sharing.

One of the perils of C++ is that it does certain things behind your back, and if you don't understand what those things are, they can bite you. One way this manifests is that apparently meaning-preserving changes can have surprising consequences.

In this recent case, I inherited some code that I was cleaning up. One of the changes was to change this:

SomeClass foo;
foo = factory.getSomeClass();
into this:
SomeClass foo = factory.getSomeClass();
Simple enough, right? But after making this change, a later access to foo segfaulted.

Can you guess why? Here's a hint: SomeClass uses a reference-counted shared-data implementation, similar to std::string.

Here's what happened: SomeClass had defined an assignment operator, but not a copy constructor. The old code used the former, and the new code used the latter. In the absence of a user-defined copy constructor, the compiler generates one that just does a bitwisememberwise copy. As a result, the reference count is copied and not incremented, and so later it becomes 0 before it should, and the data is deleted. A subsequent access to that data produces the segfault.

The solution is simple: Define a correct copy constructor. My own policy is never to rely on the compiler-generated copy constructor and assignment operator. When I write a new class, if I think I don't need these, I immediately add method prototypes for them that are private and have no implementations. Then, if you ever invoke them (deliberately or not), the compile will fail, and you'll know that you have to implement them for real (or change the calling code).

Labels: ,

Comments (0)
 
Annoyed at having to look up, yet again, one of the couple of C++ operator precedence rules that I don't have memorized (this time, whether equality or ternary is tighter), I took the C++ precedence rules from here and formatted them into a 3x5 reference card. Here it is in PDF form, or in Word if anyone wants to tweak it. (My graphic-design skills are fairly meager, so if anyone makes substantial improvements, please let me know.)

Labels: ,

Comments (0)
 
Recently I wrote some code in which a templatized class declared one of its template parameters a friend. This worked fine in Visual C++, and I was surprised to find that GCC gave the rather unambiguous error, Template parameters cannot be friends. I was even more surprised to find that, indeed, this is not GCC being overly picky - it is not legal C++! There are extremely crufty workarounds, but in general it's clearly a bad idea to rely on platform-specific violations of the C++ standard in production code.

Labels: ,

Comments (0)
 
Ned Batchelder tells a C++ debugging story, the punchline of which is this: If a C++ constructor throws an exception, then that object's destructor is called. I encountered this in my own work, and it turns out exactly the opposite: If the constructor throws, the destructor is not called. These stories have the same moral, though: Constructors really shouldn't throw exceptions. Update May 2005: I reported my findings to Ned, and he wrote an update that describes in detail the precise semantics of what happens when a C++ constructor throws an exception. In short, the destructors of every base class and member whose constructor successfully completed will be called; note that this necessarily precludes the destruction of the class whose constructor threw the exception, since that constructor obviously did not complete execution.

Labels: ,

Comments (0)
 
In last month's C/C++ Users Journal, there was a clever article that described using templates to write loop constructs that are unrolled at compile time. In my last project I wrote some multiobjective optimization code that was full of loops that went from 0 to 2 or 3; this mechanism would've been great for that. On the other hand, five years ago I highly doubt that all the compilers I supported would have processed this kind of fancy template code correctly.

Labels: ,

Comments (0)