如何将整数结构体初始化为零?

5

如何确保以下大型结构体中的整数始终初始化为0,哪种方法最好?

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

我希望避免每次检查这个结构体被初始化时都需要确认它是用Statistics s();进行值初始化而不是默认初始化的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

建议1 - 使用memset,但这感觉像是一种技巧,我们利用值初始化恰好等同于零填充数据结构:

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

建议2 - 使用构造函数初始化列表,但这是繁琐的,当人们在未来添加新计数器时,可能会忘记在构造函数中进行零初始化:

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

方案三 - 强制进行值初始化,步骤如下:

struct StatisticsUnsafe {
    // ... counters here
}

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

你认为最好的方法是什么?你有其他替代方案吗?
编辑:我想澄清一下,在我的实际代码中,每个计数器都有一个有意义的名称,例如“num_queries_received”、“num_responses”等。这就是为什么我不选择使用形如“counters[100]”的向量或数组的原因。
编辑2:将例子从Statistics s2();更改为Statistics s2{};

4
counterX最好作为一个数组。你可以使用int counter[100]{}来对元素进行值初始化。 - David G
2
请注意,Statistics s2();不会默认初始化Statistics,它声明了一个返回Statistics的函数。你可能想要使用Statistics s2{};或者auto s2=Statistics(); - Mooing Duck
@MooingDuck,我也认为Statistics s{};可以解决问题,但是被踩了👎。 - vsoftco
@vsoftco:我指出了他代码中的语法错误,而他说这不是他想要的。如果你认为他的意思是我写的那样,那么你的答案就恰恰是他所说的不想要的。 - Mooing Duck
@MooingDuck,谢谢,我现在意识到了,已经删除了我的回答。 - vsoftco
@0x499602D2 在我的实际代码中,每个计数器都有一个有意义的名称,这就是为什么我选择不使用数组的原因。编辑我的问题以反映这一点。 MooingDuck,感谢您指出我的语法错误,已经更正。 - AffluentOwl
5个回答

7

从C++11开始,您也可以这样做:

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

5

除非有特定原因需要使用其他容器,否则首选应该使用std::vector,例如:

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

这将自动将所有内容清零。您可以通过以下方式访问数组中的单个counter

++Statistics[40];

这将增加第41个项目(第一个是Statistics[0])。

如果大小确实固定在100(或其他您在编译时知道的数字),则可能更喜欢使用std :: array

std::array<int, 100> Statistics;

这种方法可能会更快,通常使用较少的内存,但固定了大小(而使用 std::vector 可以使用 push_backerase 等来添加和删除项)。

考虑到已编辑的问题(对象实际上并非类似于数组),我可能会考虑一些略有不同的方法,可能是这样:

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;
};

一个inited<T>总是被初始化为某个值--如果您愿意,可以指定一个值,如果不指定任何值,则使用值初始化(对于算术类型将给出零,对于指针类型将给出空指针,对于定义了默认构造函数的类型将使用默认构造函数)。

由于它定义了operator Toperator=,因此您仍然可以像平常一样对元素进行赋值/从中赋值:

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

你可能更喜欢使用一个单一的:

operator T&() { return val; }

然而,这不仅支持读取和写入(如上所述),还支持复合赋值运算符(例如+=-=)。


我认为我会选择std::array,但是没关系。 - Mooing Duck
@MooingDuck:好观点——已编辑。 - Jerry Coffin
2
在我的实际代码中,每个计数器都有一个有意义的名称,比如“num_queries”或“num_responses”,而不是“counter1”或“counter2”。因此,拥有counter[42]将毫无意义。最好的情况是,我可以定义一个枚举,将索引映射到枚举名称,并使用它来索引数组。所以这是另一个选择,但似乎只是为了得到0初始化而凌乱不堪。 - AffluentOwl
http://www.boost.org/doc/libs/1_55_0/libs/utility/value_init.htm - Mooing Duck
1
我喜欢你提供了一个非常安全的答案。然而,它与之相关的开销相当大,仅仅是为了确保值被初始化为零。对于向统计结构添加新计数器的人来说,我觉得他们记住使用inited<int> counterX;和记住使用C++11类成员初始化器int counterX = 0;一样费力。因此,我将接受Jarod42的答案。 - AffluentOwl
显示剩余3条评论

3
您是否考虑为每个数据成员编写一个初始化器?
struct Statistics {
  typedef int counter_t;

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

请注意,如果您包含这样的初始化程序,则结构体不再是一个聚合体,因此无法使用括号列表通过聚合初始化进行初始化。对于这种类型是否重要很难说。

3

你确实可以像这样做:

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

这在c++11中完全有效。但问题是,你真的需要这个吗?使用vector是否更方便?

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

如果向量不是一个选项,你可以在构造函数中进行一些魔法:

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.

0
这是一种类似于 C 的方式:
#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();
}

如果Statistics是标准布局,那么像这样使用memset是明确定义的。为了未来的安全性,我会在构造函数中添加static_assert(std::is_standard_layout<Statistics>(), "Someone broke Statistics, can't use memset to zero it."); - Casey
@Casey 好主意,我已经编辑了 :) - user2556165

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接