memset
, malloc
, calloc
and so on are the C-way of doing things - they're not C++-idomatic and are only really supported in C++ so you can use C code directly. Note that even in C you can use the struct someStruct = {0}
syntax to zero-initialize a struct, so using memset
with structs is unnecessary even in C. memset
is really meant for zeroing buffers, not objects.
As for his assertions about correctness, I'll say that he is factually incorrect.
Here's a laundry-list of my observations:
- It requires the programmer to manually put the first and last members in the expression to calculate
size
(and why not just use the sizeof
operator?).
- I note that example you gave is unclear in its intention: while it clears the three scalar members, is it also meant to clear the
mem
and cpumap
members? (What if another programmer added those two fields and forgot to update the constructor?)
- It fails in the case of inheritance: the value of
this
would point to the start of the topmost parent, not the first non-inherited field, so in addition to blindly overwriting parent data, you would be doing this multiple times if parent constructors have the same "initialization" logic.
- The
size
calculation happens at run-time, not compile-time, which wastes CPU cycles.
- He's using
int size
instead of size_t size
so it might not work on systems where sizeof(void*) != sizeof(int)
(e.g. x64, some obscure ISAs, certain embedded architectures, etc)
- He's blindly casting to
(char*)
even when that might be inappropriate. While sizeof(char)
is guaranteed to be 1
I don't believe it is guaranteed for char*
to always be an appropriate proxy for void*
.
- Also this is a C-style cast. In C++ the cast operators
static_cast
, reinterpret_cast
, and dynamic_cast
are always preferred over (T)
-style casts.
It makes the assumption that all members exist in the range defined by their declaration order. You cannot make this assumption in C++ (see here: Do class/struct members always get created in memory in the order they were declared? ) because the 1998 and 2003 specifications state:
The order of allocation of nonstatic data members separated by an access-specifier is unspecified
So his code would depend upon undefined-behaviour in this case:
struct Foo {
private:
int a;
int b;
public:
int c;
private:
int d;
}
Foo::Foo() {
int size = (char*)&this.d - (char*)&this.a;
}
Dangerously, you cannot make assumptions that a zeroed member is "valid" - the implementation of std::map
and std::string
might have internal members which cannot be zero, by blindly wiping them you put them into an unknown state. This is dangerous.
Point is: do not do this.
The C++ way is using initialization lists, which offer a lot of compile-time safety and guarantees, and require an explicit initial value which is guaranteed to be type-safe. The syntax is:
struct Foo {
someType x;
int y;
foo bar;
};
Foo:Foo() :
x(0),
y(0),
bar(some_initial_bar_value) {
// any sequential init logic goes here
}