是否可以防止遗漏聚合初始化成员?
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
我有一个包含许多相同类型成员的结构,就像这样
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