In both x86-64 GCC 13.1 and Clang 16.0.0, the copy<PrivateBase>
function uses member-wise copy, while the copy<PublicBase>
function uses bit-wise copy. You could refer to the detailed source code and assembly code on the compiler explorer or see the code snippets provided below:
class PublicBase {
public:
int num;
char c1;
};
class PrivateBase {
private:
int num;
char c1;
};
template<typename T>
__attribute_noinline__ void copy(T *dst, T *src) {
*dst = *src;
}
template void copy(PublicBase *dst, PublicBase *src);
template void copy(PrivateBase *dst, PrivateBase *src);
void copy<PublicBase>(PublicBase*, PublicBase*):
mov rax, QWORD PTR [rsi]
mov QWORD PTR [rdi], rax
ret
void copy<PrivateBase>(PrivateBase*, PrivateBase*):
mov eax, DWORD PTR [rsi]
mov DWORD PTR [rdi], eax
movzx eax, BYTE PTR [rsi+4]
mov BYTE PTR [rdi+4], al
ret
The question is, when does the default copy assignment operator from C++11 use bit-wise copy instead of member-wise copy? It seems that neither is_trivially_copyable
nor is_pod
provides the answer.
is_trivially_copyable
According to cppreference-is_trivially_copyable:
Objects of trivially-copyable types that are not potentially-overlapping subobjects are the only C++ objects that may be safely copied with std::memcpy.
Both PublicBase
and PrivateBase
are trivially copyable and not subobjects, but PrivateBase
is copied with member-wise instead of bit-wise.
is_pod
If there is a derived class of PublicBase
or PrivateBase
, the derived class of PrivateBase
will reuse the padding of the base class, while that of PublicBase
won't.
Therefore, it is reasonable that PrivateBase
is copied with member-wise. Otherwise, the padding of base class may overwrite PrivateDerived::c2
when calling copy<PrivateBase>(derived, base)
.
class PublicDerived : public PublicBase {
public:
char c2;
};
class PrivateDerived : public PrivateBase {
private:
char c2;
};
int main() {
std::cout << "sizeof(PublicBase)=" << sizeof(PublicBase) << std::endl;
std::cout << "sizeof(PublicDerived)=" << sizeof(PublicDerived) << std::endl;
std::cout << "sizeof(PrivateBase)=" << sizeof(PrivateBase) << std::endl;
std::cout << "sizeof(PrivateDerived)=" << sizeof(PrivateDerived) << std::endl;
return 0;
}
// Output:
// sizeof(PublicBase)=8
// sizeof(PublicDerived)=12
// sizeof(PrivateBase)=8
// sizeof(PrivateDerived)=8
I am confused about how the compiler decides to reuse padding of the base class or not. According to the related question, the POD type doesn't reuse padding of the base class.
According to the cppreference-POD_class:
A POD class is a class that
- until C++11:
- is an aggregate (no private or protected non-static data members),
- has no user-declared copy assignment operator,
- has no user-declared destructor, and
- has no non-static data members of type non-POD class (or array of such types) or reference.
- since C++11
- is a trivial class,
- is a standard-layout class (has the same access control for all non-static data members), and
- has no non-static data members of type non-POD class (or array of such types).
before C++11, PrivateBase
is not POD type (because it has private data members), but since C++11, it becomes POD type (because it has the same access control for all non-static data members).
int main() {
std::cout << "PublicBase: is_standard_layout=" << is_standard_layout<PublicBase>::value
<< ", is_trivial=" << is_trivial<PublicBase>::value
<< ", is_pod=" << is_pod<PublicBase>::value << std::endl;
std::cout << "PrivateBase: is_standard_layout=" << is_standard_layout<PrivateBase>::value
<< ", is_trivial=" << is_trivial<PrivateBase>::value
<< ", is_pod=" << is_pod<PrivateBase>::value << std::endl;
}
// Output:
// PublicBase: is_standard_layout=1, is_trivial=1, is_pod=1
// PrivateBase: is_standard_layout=1, is_trivial=1, is_pod=1
Aucun commentaire:
Enregistrer un commentaire