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.
std::string whatever = "abcdefgh";
std::string whatever2 = whatever;
The first constructor looks like this
basic_string(const value_type* _String,
const allocator_type& _Allocator = allocator_type() ) : _Parent(_Allocator)
{
const size_type _StringSize = traits_type::length(_String);
if (_MySmallStringCapacity < _StringSize)
{
_AllocateAndCopy(_String, _StringSize);
}
else
{
traits_type::copy(_MySmallString._Buffer, _String, _StringSize);
_SetSmallStringCapacity();
_SetSize(_StringSize);
}
}
The generated code is
std::string whatever = "abcdefgh";
000000013FCC30C3 mov rdx,qword ptr [string "abcdefgh" (13FD07498h)]
000000013FCC30CA mov qword ptr [whatever],rdx
000000013FCC30D2 mov byte ptr [rsp+347h],0
000000013FCC30DA mov qword ptr [rsp+348h],8
000000013FCC30E6 mov byte ptr [rsp+338h],0
Here traits_type::copy
contains a call to memcpy
, which is optimized into a single register copy of the whole string (carefully selected to fit). The compiler also transforms a call to strlen
into a compile time 8
.
Then we copy it into a new string. The copy constructor looks like this
basic_string(const basic_string& _String)
: _Parent(std::allocator_traits<allocator_type>::select_on_container_copy_construction(_String._MyAllocator))
{
if (_MySmallStringCapacity < _String.size())
{
_AllocateAndCopy(_String);
}
else
{
traits_type::copy(_MySmallString._Buffer, _String.data(), _String.size());
_SetSmallStringCapacity();
_SetSize(_String.size());
}
}
and results in just 4 machine instructions:
std::string whatever2 = whatever;
000000013FCC30EE mov qword ptr [whatever2],rdx
000000013FCC30F6 mov byte ptr [rsp+6CFh],0
000000013FCC30FE mov qword ptr [rsp+6D0h],8
000000013FCC310A mov byte ptr [rsp+6C0h],0
Note that the optimizer remembers that the char
's are still in register rdx
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With