45

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

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

问题是,如果我忘记初始化结构成员之一(例如wasactive),如下所示:

VariablePointers{activePtr, filename}

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

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

4

5 回答 5

45

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

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),但应该适用于任何健全的平台。
于 2020-02-10T13:04:01.860 回答
23

对于 clang 和 gcc,您可以使用-Werror=missing-field-initializers它将缺少字段初始值设定项的警告转换为错误进行编译。神螺栓

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

于 2020-02-10T13:18:07.993 回答
7

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

这个想法是在你的结构中添加一个额外的成员,在最后一个位置,一个没有默认初始化的类型(并且不能用一个类型的值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示例中为 的整数),或者您收到“调用已删除的 '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值被删除的模板构造函数截获

于 2020-02-10T14:30:00.833 回答
4

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

更新:

您要检查的规则是 typesafety 的一部分Type.6

Type.6:始终初始化成员变量:始终初始化,可能使用默认构造函数或默认成员初始化器。

于 2020-02-10T13:30:57.960 回答
2

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

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

如果初始化子句的数量小于成员数或初始化列表完全为空,则剩余成员被值初始化。如果引用类型的成员是这些剩余成员之一,则程序格式错误。

另一种选择是采用 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
于 2020-02-10T16:28:31.433 回答