You're not supposed to treat object pointers as pointers to raw binary data in OOP languages, including C++. Objects are "more than" their representation.
So, for example, swap
ing two objects by swapping their bytes is incorrect:
template<class T>
void bad_swap(T &a, T &b) // Assuming T is the most-derived type of the object
{
char temp[sizeof(T)];
memcpy(temp, &a, sizeof(a));
memcpy(&a, &b, sizeof(b));
memcpy(&b, temp, sizeof(temp));
}
The only situation, however, in which I can imagine this shortcut causing a problem is when an object contains a pointer to itself, which I have rarely (never?) seen in practice; there may, though, also be other scenarios.
What are some actual (real-world) examples of when a correct swap
would break if you performed a bitwise swap?
I can easily come up with contrived examples with self-pointers, but I can't think of any real-world ones.
This is not specifically about
swap
but an example showing that low level optimizations are maybe not worth the trouble. The compiler often figures it out anyway.Of course, this is my favorite example where the compiler is exceptionally lucky, but anyway we shouldn't assume that compilers are stupid and that we can easily improve on the generated code with some simple tricks.
My test code is - construct a std::string and copy it.
The first constructor looks like this
The generated code is
Here
traits_type::copy
contains a call tomemcpy
, which is optimized into a single register copy of the whole string (carefully selected to fit). The compiler also transforms a call tostrlen
into a compile time8
.Then we copy it into a new string. The copy constructor looks like this
and results in just 4 machine instructions:
Note that the optimizer remembers that the
char
's are still in registerrdx
and that the string length must be the same,8
.It is after seeing things like this that I like to trust my compiler, and avoid trying to improve code with bit fiddling. It doesn't help, unless profiling finds an unexpected bottleneck.
(featuring MSVC 10 and my std::string implementation)
Why are "self-pointers" contrived?
This type holds a buffer and a current position into the buffer.
Or maybe you've heard of iostreams:
As someone else mentioned, the small string optimisation:
This has a self-pointer too. If you construct two small strings then swap them, the destructors will both decide the string is "non-local" and try to delete the memory:
Valgrind says:
So that shows it affects types like
std::streambuf
andstd::string
, hardly contrived or esoteric examples.Basically,
bad_swap
is never a good idea, if the types are trivially-copyable then the defaultstd::swap
will be optimal (of your compiler doesn't optimise it to memcpy then get a better compiler) and if they're not trivially-copyable it's a great way to meet Mr. Undefined Behaviour and his friend Mr. Serious Bug.Some not already mentioned:
I'm going to argue that this is almost always a bad idea except in the specific case where profiling has been done and a more obvious and clear implementation of
swap
has performance problems. Even in that case I would only go with this sort of approach for straight up no-inheritance structures, never for any sort of class. You never know when inheritance will be added potentially breaking the whole thing (possibly in truly insidious ways too).If you want a fast swap implementation perhaps a better choice (where appropriate) is to pimpl the class and then just swap out the implementation (again, this assumes that there are no back-pointers to the owner, but that's easily contained to the class & impl rather than external factors).
EDIT: Possible problems with this approach:
Besides the examples mentioned in other answers (particulary objects containing pointers to parts of themselves and objects needing locking), there could also be a case of pointers to the object being managed by an external datastructure, which needs to be updated accordingly (please note that the example is somewhat contrived in order to not be excessive (and maybe buggy by virtue of not having been tested)):
obviously something like this would break badly if objects are swapped by
memcpy
. Of course real world examples for this are typically somewhat more complex, but the point should be clear.Besides the examples I think that copying (or swapping) non trivially copyable objects like this is undefined behaviour by the standard (might check that later). In that case there would be no guarantee at all for that code to work with more complex objects.