是否可以防止省略聚合初始化成员?

Is it possible to prevent omission of aggregate initialization members?

本文关键字:初始化 成员 止省 是否      更新时间:2023-10-16

我有一个结构,其中包含许多相同类型的成员,如下所示

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进行编译,将缺少字段初始值设定项的警告转换为错误。 戈博尔特

编辑:对于 MSVC,即使在级别/Wall似乎也没有发出警告,所以我认为不可能使用此编译器警告缺少初始值设定项。 戈博尔特

我想这不是一个优雅而方便的解决方案......但应该也适用于 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值被已删除的模板构造函数截获

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

更新:

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

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

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

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

从 cpp首选项 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