是否可以防止遗漏聚合初始化成员?

Is it possible to prevent omission of aggregate initialization members?

我有一个包含许多相同类型成员的结构,就像这样

struct VariablePointers {
   VariablePtr active;
   VariablePtr wasactive;
   VariablePtr filename;
};

问题是如果我忘记初始化其中一个结构成员(例如 wasactive),就像这样:

VariablePointers{activePtr, filename}

编译器不会报错,但我会得到一个部分初始化的对象。我怎样才能防止这种错误?我可以添加一个构造函数,但它会复制变量列表两次,所以我必须输入所有这些三次!

如果有针对 C++11 的解决方案(目前我仅限于该版本),还请添加 C++11 答案。不过,也欢迎更新的语言标准!

如果缺少必需的初始化程序,这里有一个触发链接器错误的技巧:

struct init_required_t {
    template <class T>
    operator T() const; // Left undefined
} static const init_required;

用法:

struct Foo {
    int bar = init_required;
};

int main() {
    Foo f;
}

结果:

/tmp/ccxwN7Pn.o: In function `Foo::Foo()':
prog.cc:(.text._ZN3FooC2Ev[_ZN3FooC5Ev]+0x12): undefined reference to `init_required_t::operator int<int>() const'
collect2: error: ld returned 1 exit status

注意事项:

  • 在 C++14 之前,这会阻止 Foo 成为聚合。
  • 这在技术上依赖于未定义的行为(违反 ODR),但应该可以在任何健全的平台上运行。

对于 clang 和 gcc,您可以使用 -Werror=missing-field-initializers 进行编译,这会将缺少字段初始值设定项的警告变为错误。 godbolt

编辑: 对于 MSVC,即使在级别 /Wall 似乎也没有发出警告,所以我认为不可能在缺少初始化程序时发出警告用这个编译器。 godbolt

对于 CppCoreCheck 有一个规则可以准确地检查,如果所有成员都已经初始化并且可以从警告变成错误 - 当然这通常是程序范围的。

更新:

您要检查的规则是类型安全的一部分Type.6:

Type.6: Always initialize a member variable: always initialize, possibly using default constructors or default member initializers.

我想这不是一个优雅和方便的解决方案......但也应该适用于 C++11 并给出编译时(不是 link-时间)错误。

这个想法是在你的结构中添加一个额外的成员,在最后一个位置,一个没有默认初始化的类型(并且不能用 VariablePtr 类型的值初始化(或者任何类型的前面的值)

举例

struct bar
 {
   bar () = delete;

   template <typename T> 
   bar (T const &) = delete;

   bar (int) 
    { }
 };

struct foo
 {
   char a;
   char b;
   char c;

   bar sentinel;
 };

这样你就被迫在聚合初始化列表中添加所有元素,包括显式初始化最后一个值的值(在示例中为 sentinel 的整数)或者你得到一个 "call to deleted constructor of 'bar'"错误。

所以

foo f1 {'a', 'b', 'c', 1};

编译和

foo f2 {'a', 'b'};  // ERROR

不会。

可惜也

foo f3 {'a', 'b', 'c'};  // ERROR

无法编译。

-- 编辑 --

正如 MSalters 所指出的(谢谢),我的原始示例中存在一个缺陷(另一个缺陷):bar 值可以用 char 值(可转换为 int), 所以下面的初始化工作

foo f4 {'a', 'b', 'c', 'd'};

这可能会让人非常困惑。

为避免此问题,我添加了以下已删除的模板构造函数

 template <typename T> 
 bar (T const &) = delete;

所以前面的f4声明给出了一个编译错误,因为d值被删除的模板构造函数拦截

最简单的方法是不给成员的类型一个无参数的构造函数:

struct B
{
    B(int x) {}
};
struct A
{
    B a;
    B b;
    B c;
};

int main() {

        // A a1{ 1, 2 }; // will not compile 
        A a1{ 1, 2, 3 }; // will compile 

另一种选择:如果您的成员是 const & ,则必须初始化所有成员:

struct A {    const int& x;    const int& y;    const int& z; };

int main() {

//A a1{ 1,2 };  // will not compile 
A a2{ 1,2, 3 }; // compiles OK

如果你能忍受一个虚拟的 const & 成员,你可以将它与@max66 的哨兵想法结合起来。

struct end_of_init_list {};

struct A {
    int x;
    int y;
    int z;
    const end_of_init_list& dummy;
};

    int main() {

    //A a1{ 1,2 };  // will not compile
    //A a2{ 1,2, 3 }; // will not compile
    A a3{ 1,2, 3,end_of_init_list() }; // will compile

来自 cppreference https://en.cppreference.com/w/cpp/language/aggregate_initialization

If the number of initializer clauses is less than the number of members or initializer list is completely empty, the remaining members are value-initialized. If a member of a reference type is one of these remaining members, the program is ill-formed.

另一种选择是采用 max66 的哨兵思想并添加一些语法糖以提高可读性

struct init_list_guard
{
    struct ender {

    } static const end;
    init_list_guard() = delete;

    init_list_guard(ender e){ }
};

struct A
{
    char a;
    char b;
    char c;

    init_list_guard guard;
};

int main() {
   // A a1{ 1, 2 }; // will not compile 
   // A a2{ 1, init_list_guard::end }; // will not compile 
   A a3{ 1,2,3,init_list_guard::end }; // compiles OK