5

What is the best way to make sure the following large struct always has its integers initialized to 0?

struct Statistics {
    int num_queries;
    int num_respones;
    // ... 97 more counters here
    int num_queries_filtered;
}

I would like to avoid having to check each place this struct is initialized to make sure it is value initialized with Statistics s(); rather than default initialized with Statistics s;.

Statistics s;     // Default initialized by accident here
s.num_queries++;  // Oh no, this is a bug because it wasn't initialized to zero
Statistics s2{};  // Correctly value initialized
s2.num_queries++; // Successful

Proposal 1 - Use memset, but this feels like a hack where we take advantage of the value initialization happening to be equivalent to 0 filling the data structure:

struct Statistics {
    Statistics() { memset(this, 0, sizeof(*this)); }
    // ... counters here
}

Proposal 2 - Use constructor initialization lists, but this is cumbersome and when people add new counters in the future they may forget to zero-initialize them in the constructor:

struct Statistics {
    Statistics() : num_queries(0), num_respones(0), /* ... */, num_queries_filtered(0) {}
    // ... counters here
}

Proposal 3 - Force the value initialization to take place as follows:

struct StatisticsUnsafe {
    // ... counters here
}

struct Statistics : public StatisticsUnsafe {
    Statistics() : StatisticsUnsafe() {}
}

What do you feel is the best approach? Do you have other alternatives?

EDIT I want to clarify that in my actual code, each of the counters has a meaningful name, such as "num_queries_received", "num_responses", etc. Which is why I do not opt to use a vector or array of the form "counters[100]"

EDIT2 Changed the example from Statistics s2(); to Statistics s2{};

AffluentOwl
  • 3,337
  • 5
  • 22
  • 32
  • 4
    `counterX` is better off as an array. And you can do `int counter[100]{}` to value-initialize the elements. – David G Oct 09 '14 at 19:27
  • 2
    Note that `Statistics s2();` does _NOT_ default initialize a `Statistics`, it declared a function that returns a `Statistics`. You meant `Statistics s2{};` or maybe `auto s2=Statistics();` – Mooing Duck Oct 09 '14 at 19:30
  • @MooingDuck, I also thought `Statistics s{};` will do the trick, but got downvoted :) – vsoftco Oct 09 '14 at 19:32
  • @vsoftco: I was pointing out a syntax error in code he said was not what he wanted. If you assume he meant what I wrote, then your answer is _exactly_ what he said he did not want. – Mooing Duck Oct 09 '14 at 19:34
  • @MooingDuck, thanks, I realized that now, deleted my answer. – vsoftco Oct 09 '14 at 19:36
  • @0x499602D2 In my actual code each counter has a meaningful name, which is why I chose not to use an array. Editing my question to reflect this. MooingDuck, Thanks for pointing out my syntax error, corrected it. – AffluentOwl Oct 09 '14 at 19:41

5 Answers5

7

From C++11, you may also do:

struct Statistics {
    int counter1 = 0;
    int counter2 = 0;
    // ... more counters here
    int counter100 = 0;
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
5

Unless you have a fairly specific reason to do otherwise, your first choice should probably be a std::vector, such as:

std::vector<int> Statistics(100);

This will zero all the contents automatically. You can address an individual counter in the array as something like:

++Statistics[40];

...which will increment the 41st item (the first is Statistics[0]).

If the size if really fixed at 100 (or some other number you know at compile time) you might prefer to use std::array instead:

std::array<int, 100> Statistics;

This is potentially a little faster and usually uses a (little) less memory, but fixes the size (whereas with an std::vector you can use push_back, erase, etc., to add and remove items).

Given the edited question (the objects really aren't array-like) I'd probably consider something a little different, probably something like this:

template <class T>
class inited {
    T val;
public:
    inited(T val=T()) : val(val) {}
    operator T() const { return val; }
    operator=(T const &newval) { val = new_val; }
};

struct Statistics { 
    inited<int> sum;
    inited<int> count;
    inited<double> mean;
};

Then an inited<T> is always initialized to some value--you can specify a value if you wish, and if you don't specify any, it uses value initialization (which will give zero for arithmetic types, a null pointer for a pointer type, or use the default constructor for types that define one).

Since it defines an operator T and an operator=, you can still assign to/from elements, just about like usual:

Statistics.sum = 100;
Statistics.count = 2;
Statistics.mean = static_cast<double>(Statistics.sum) / Statistics.count;

You might prefer to use a single:

operator T&() { return val; }

Instead though. This supports both reading and writing (as above) but also compound assignment operators (e.g., += and -=).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • I think I would have gone with `std::array` but yeah. – Mooing Duck Oct 09 '14 at 19:34
  • @MooingDuck: Good point--edited. – Jerry Coffin Oct 09 '14 at 19:37
  • 2
    In my actual code, each counter has a meaningful name, like "num_queries" or "num_responses" instead of "counter1" or "counter2". So it would be meaningless to have counter[42]. At best, I could define an enum which maps from indexes to enum names, and use that to index into the array. So that's another option, but seems messy just to get the 0 initialization. – AffluentOwl Oct 09 '14 at 19:44
  • @AffluentOwl, wrote a comment on your main question about using a `Statistics` as a wrapped class inside another `Wrapper` class, and the `Wrapper` will do the initialization. Not super elegant, but allows you to keep your vars names with the price of an extra structure. – vsoftco Oct 09 '14 at 19:46
  • http://www.boost.org/doc/libs/1_55_0/libs/utility/value_init.htm – Mooing Duck Oct 09 '14 at 20:09
  • @MooingDuck: Interesting, but (IMO) less than ideal--I prefer one that supports value initialization, but also supports initialization with other values when desired. – Jerry Coffin Oct 09 '14 at 20:14
  • @JerryCoffin: Agreed, I'm rather surprised it can't do that yet. – Mooing Duck Oct 09 '14 at 21:06
  • 1
    I like that you provide a very safe answer. However, it has quite a bit of overhead associated with it, just for making sure values are zero initialized. For people adding a new counter to the statistics struct, I feel that it is just as much overhead for them to remember to use an `inited counterX;` as it is for them to remember to use C++11 in-class member initializers `int counterX = 0;`. Therefore, I am going to accept Jarod42's answer. – AffluentOwl Oct 09 '14 at 21:18
3

Have you considered writing an initializer for each data member?

struct Statistics {
  typedef int counter_t;

  counter_t counter1 = 0;
  counter_t counter2 = 0;
  // ... more counters here
  counter_t counter100 = 0;
};

Note that if you include such initializers, though, the struct is no longer an aggregate, and hence can't be initialized using aggregate initialization via a braced list. Whether that matters or not for this type is hard to say.

seh
  • 14,999
  • 2
  • 48
  • 58
3

Well you certainly can do something like:

struct Statistics {
    int counter1 = 0;
    int counter2 = 0;
    // ... more counters here
    int counter100 = 0;
};

This is perfectly valid in c++11. But the question is, do you really need this? Wouldn't it be more convenient to use a vector?

struct Statistics {
    std::vector<int> counters = std::vector<int>(100, 0);
};

And if vector is not an option, you can do some magic in constructor:

struct Statistics {
    int counter1;
    int counter2;
    // ... more counters here
    int counter100;

    Statistics() {
        for (int * i : {&counter1, &counter2, ..., &counter100 }) {
            *i = 0;
        }
    }
};
Statistics s;
s.counter2; // now stores 0 or anything you like.
Jendas
  • 3,359
  • 3
  • 27
  • 55
0

Here is a C-like way:

#include <assert.h>
#include <cstring>
#include <type_traits>

struct Statistics {
  int counter1;
  int counter2;
  int counter3;
  int counter4;
  // maybe more //
  Statistics() {
    // checks whether Statistics is standard-layout
    // to be sure that memset won't break it
    static_assert(
        std::is_standard_layout<Statistics>(),
        "Someone broke Statistics, can't use memset to zero it.");
    // initializes hole Statistics's memory by zeros
    memset(this, 0, sizeof(Statistics));
  }
};

// Here is a way how to check Statistics
void assert_Statistics() {
  Statistics s;
  int* ptr   = reinterpret_cast<int*>(&s);
  int  count = sizeof(Statistics) / sizeof(int);
  for (int i = 0; i < count; ++i) {
    assert(*(ptr++) == 0);
  }
}

int main()
{
  Statistics s;
  assert_Statistics();
}
  • Use of `memset` like this is only well-defined if `Statistics` is standard-layout. I would add a `static_assert(std::is_standard_layout(), "Someone broke Statistics, can't use memset to zero it.");` in the constructor for future-proofing. – Casey Oct 09 '14 at 20:26
  • @Casey good idea, I edited :) –  Oct 09 '14 at 21:04