The curious case of the missing copy constructor

| 0 Comments
I have a tendency to write unit tests that are a little more invasive than they need to be; these tests make sure that not only are the results as expected but also that as many of the side-effects and interactions with other objects are as expected as well. So, for example, in my current WebSockets development for The Server Framework I have some tests which test that the correct data is delivered to the client of the API that I'm developing and also test that the API interacts with its buffer allocator correctly and doesn't leak memory. The detailed interaction testing sometimes gets in the way of refactoring as the inputs don't change, the outputs don't change but the interaction changes so a test that should pass before and after a refactoring may fail just because I've optimised the use of a sub object that happens to be being checked in the test; in these cases where both levels of testing are useful I sometimes duplicate the test with and without the detailed interaction examination... Anyway...

I have a test which tracks the way a buffer's reference count is modified as the object under test performs an action; it's useful to be have tests which prove that under given circumstances you don't have a reference counting leak. The buffer is passed around via a mix of raw pointers and smart pointers and during the test a mock buffer is used which logs all of the operations performed. At one point in the interaction log there's a point where the buffer is returned by value via a smart pointer and we get an AddRef(), Release() sequence of calls as the temporary is copied into and out of. The compiler is allowed by the C++ standard to elide copy constructors in some situations, see here, so you should be careful that your copy constructor doesn't do anything other than copy the object as you can't guarantee that it will actually be called. If you then allow for the fact that the compiler may opt to use the Return Value Optimisation in some circumstances to avoid creating temporaries you should probably start to realise that my invasive test is somewhat fragile depending on which compiler optimisations are enabled and which compiler is being used...

So, my tests expect to see the copy constructor called and the resulting sequence of AddRef() and Release() calls on the buffer and in debug builds on all supported compilers this is what they see. Unfortunately on release builds the compilers differ... VS2005 and VS2010 RTM both elide the copy constructor (presumably applying RVO) whereas VS2008 and VS2010 SP1 both call the copy constructor. For now I have a rather clunky macro that detects the compiler version and build version and removes the test requirement where necessary. For a while I had some problems differentiating between VS2010 RTM and VS2010 SP1 but luckily _MSC_FULL_VER can be used for that.

Now, off to learn a little more about RVO and move constructors.

Edit:It seems that it's less compiler related than I thought. One of the projects (the one where the smart pointer template lives) had optimisations turned off in some of the release builds for some of the compilers... More investigation needed, with any luck all compilers will elide the copy once optimisations are turned on...

Leave a comment