1

I've inherited a C++98 codebase which has two major uses of memset() on C++ classes, with macros expanded for clarity:

 // pattern #1:
 Obj o;
 memset(&o, 0, sizeof(o));

 // pattern #2:
 // (elsewhere: Obj *o;)
 memset(something->o, 0, sizeof(*something->o));

As you may have guessed, this codebase does not use STL or otherwise non-POD classes. When I try to put as little as an std::string into one of its classes, bad things generally happen.

It was my understanding that these patterns could be rewrited as follows in C++11:

 // pattern #1
 Obj o = {};

 // pattern #2
 something->o = {};

Which is to say, assignment of {} would rewrite the contents of the object with the default-initialized values in both cases. Nice and clean, isn't it?

Well, yes, but it doesn't work. It works on *nix systems, but results in fairly inexplicable results (in essence, garbage values) when built with VS2013 with v120_xp toolset, which implies that my understanding of initializer lists is somehow lacking.

So, the questions:

  1. Why didn't this work?
  2. What's a better way to replace this use of memset that ensures that members with constructors are properly default-initialized, and which can preferably be reliably applied with as little as search-and-replace (there are unfortunately no tests). Bonus points if it works on pre-VS2013.
Catherine
  • 22,492
  • 3
  • 32
  • 47
  • Yeah empty brace initializers don't work as well as one would think, I've run into many bugs around members not getting zeroed out fully. Unfortunately I don't think there is any quick fix to generically initialize the members of a C++ class... It's my understanding that you have to explicitly initialize the members – kcraigie Nov 05 '15 at 06:42
  • Did `Obj o = {};` work? – personjerry Nov 05 '15 at 06:48
  • Also are you sure your class is an aggregate class? See https://stackoverflow.com/questions/4178175/what-are-aggregates-and-pods-and-how-why-are-they-special – personjerry Nov 05 '15 at 07:05
  • @personjerry Ahhh, some of the classes have base classes--though not even for inheritance, just to hide some data when the code is built as a library--and indeed, one of those was filled with garbage values on MSVC! Thank you for that link. – Catherine Nov 05 '15 at 08:11

3 Answers3

2

The behavior of brace-initialization depends on what kind of object you try to initialize.

On aggregates (e.g. simple C-style structures) using an empty brace-initializer zero-initializes the aggregate, i.e. it makes all members zero.

On non-aggregates an empty brace-initializer calls the default constructor. And if the constructor doesn't explicitly initialize the members (which the compilers auto-generated constructor doesn't) then the members will be constructed but otherwise uninitialized. Members with their own constructors that initialize themselves will be okay, but e.g. an int member will have an indeterminate value.

The best way to solve your problems, IMO, is to add a default constructor (if the classes doesn't have it already) with an initializer list that explicitly initializes the members.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 2
    Non-aggregate that does not explicitly initialize members but for which the spec guarantees that the members will be zero-initialized when brace initialization is used: [example code](http://melpon.org/wandbox/permlink/gD7YEjKMOMwvopsf). VC++ doesn't provide the correct behavior in this case, however. – bames53 Nov 05 '15 at 07:18
  • I've added such a default constructor to non-aggregate classes, but something's still wrong on MSVC--I get a stack overflow on `*this = {}` in a POD class (this one: https://github.com/whitequark/solvespace/blob/for-upstream/src/ui.h#L11), which just makes no sense whatsoever. – Catherine Nov 05 '15 at 11:47
  • It turned out that the POD class is 2.6 megabytes long and MSVC does not optimize the assignment into memset. So this worked after changing that one instance back to an explicit memset call, thank you very much! – Catherine Nov 05 '15 at 13:37
1

It works on *nix systems, but results in fairly inexplicable results (in essence, garbage values) when built with VS2013 with v120_xp toolset, which implies that my understanding of initializer lists is somehow lacking.

The rules for 'default' initialization have changed from version to version of C++, but VC++ has stuck with the C++98 rules, ignoring even the updates from C++03 I think.

Other compilers have implemented new rules, with gcc at one point even implementing some defect resolutions that hadn't been accepted for future inclusion in the official spec.

So even though what you want is guaranteed by the standard, for the most part it's probably best not to try to rely on the behavior of initialization of members that don't have explicit initializers.

bames53
  • 86,085
  • 15
  • 179
  • 244
-1

I think placement new is established enough that it works on VS, so you might try:

#include <new>

new(&o) T();
new(something->p) T();

Make sure not to do this on any object that hasn't been allocated and destructed/uninitialized first! (But it was pointed out below that this might fail if a constructor throws an exception.)

You might be able to just assign from a default object, that is, o = T(); or *(something->p) = T();. A good general strategy might be to give each of these POD classes a trivial default constructor with : o() in the initializer-list.

Davislor
  • 14,674
  • 2
  • 34
  • 49
  • 1
    This is really not a good idea. If these objects have automatic lifetimes (like in the question) and the constructor throws, the destructor that is called when the object goes out of scope will cause undefined behaviour (because it will be called on an already-destructed object). – Mankarse Nov 05 '15 at 07:29
  • @Mankarse If VS2013 doesn't support brace-initializers, it probably doesn't support `noexcept` either. Edited. – Davislor Nov 05 '15 at 07:45