A bit-wise copy is a shallow copy but not necessarily vice versa.
The reason is that due to padding there can be bits in objects that you usually ignore, yet they are part of an object.
For example this
struct bar {
int x;
foo b;
};
Can lool like this in memory:
| x | some padding | b | more padding |
When you copy the bits via eg memcpy
then the padding bits will be copied as well. By comparing the members x
and b
you cannot tell the difference, but at the level of bits there is a difference. You can notice it when you compare two objects bit wise (instead of member-wise).
As pointed out by Yakk - Adam Nevraumont in a comment, padding is not the only reason why a shallow copy can differ from a bitwise copy. For example
struct foo{
foo* self;
foo() : self(this) {}
foo& operator=(const foo& other) {}
foo(const foo& other) : self(this) {}
};
The member self
should point to the object itself, thats an invariant of the class (proper encapsulation omitted for the sake of simplicity). Just copying the pointer would make self->this
point to other
not to this
, hence break the invariant. The operator=
does not have to copy anything and a copy constructor merely has to properly initialize self
. This can be considered as a shallow copy, because we are only "copying" the pointer self
not what it points to (and actually a deep copy would be fatal here). However, a bitwise copy would be different, it would cause a.self
point to b
after copying b
to a
(which again would break the invariant).
Consider this example:
#include <iostream>
#include <cstring>
struct X {
int a = 1;
double b = 2;
float c = 3;
X& operator=(const X& x){
a = x.a;
b = x.b;
c = x.c;
return *this;
}
};
int main()
{
X a;
X b;
std::cout << "sizeof(X) " << sizeof(X) << "\n";
std::cout << "sizeof(int) " << sizeof(int) << "\n";
std::cout << "sizeof(double) " << sizeof(double) << "\n";
std::cout << "sizeof(float) " << sizeof(float) << "\n";
//memcpy(&a,&b,sizeof(X));
a = b;
char* aptr = reinterpret_cast<char*>(&a);
char* bptr = reinterpret_cast<char*>(&b);
for (size_t i = 0; i < sizeof(X); ++i) {
if (aptr[i] != bptr[i]) std::cout << " !!! ";
}
}
Possible output is:
sizeof(X) 24
sizeof(int) 4
sizeof(double) 8
sizeof(float) 4
!!!
The size of X
is not the sum of the size of its members. That is padding. For more details I refer you to Struct padding in C++.
The operator=
does a member-wise copy. Because the object contain bytes that are not used for the members, you can observe a difference between a
and b
after copying the members and looking at the bit representation.
On the other hand, if the copy was made via memcpy
then the bit representation of a
and b
would be guaranteed to be identical (ie no output !!!
).