添加 initializer_list 构造函数后静默中断构造函数调用
Silent breaking of constructor calls after adding initializer_list constructor
让我们考虑以下问题:
#include <iostream>
#include <initializer_list>
class Foo {
public:
Foo(int) {
std::cout << "with int\n";
}
};
int main() {
Foo a{10}; // new style initialization
Foo b(20); // old style initialization
}
在 运行 它打印:
with int
with int
一切顺利。现在由于新的要求,我添加了一个构造函数,它接受一个初始化列表。
Foo(std::initializer_list<int>) {
std::cout << "with initializer list\n";
}
现在打印:
with initializer list
with int
所以我的旧代码 Foo a{10}
悄无声息地被破坏了。 a
应该用 int
初始化。
我了解语言语法将 {10}
视为包含一项的列表。但是我怎样才能防止旧代码的这种无声破坏?
- 是否有任何编译器选项会在这种情况下向我们发出警告?因为这将是特定于编译器的,所以我对 gcc 最感兴趣。我已经试过了
-Wall -Wextra
.
- 如果没有这样的选项,那么我们是否总是需要使用旧式构造,即使用
()
Foo b(20)
,对于其他构造函数,只有在我们真正想要的时候才使用 {}
初始化列表?
我找不到这样的选项,所以显然在这种情况下,您应该为具有 initializer_list 构造函数的 类 使用括号,并为所有其他 类 使用统一初始化作为你希望。
可以在此答案和评论中找到一些有用的见解:
没有编译器警告,也永远不会有。警告代码做一些常见的事情是没有意义的,比如
std::vector vec{1};
请记住,编译器只会警告 真正 不需要的东西,比如未定义的行为。它无法知道在上面的定义中,您打算调用带有大小参数的构造函数。据它所知,您实际上想要 有一个只有一个元素的向量!它无法读懂你的想法:)
你第二个问题的答案基本是肯定的。你总是可以添加一个像 struct {} dummy;
这样的虚拟参数来避免使用带有初始化列表的构造函数,但实际上,唯一相同的解决方案只是使用圆括号而不是大括号(或者不要突然破坏接口)。
如果要更改所有使用列表初始化的代码,可以删除初始化列表构造函数,将它们更改为大括号,然后然后正确实现构造函数。我会认为这样的改变是一个破坏性的改变,并适当地处理它。另一个想法是事先提出初始化列表用例,并立即实施。
在这些情况下不可能生成任何警告,因为呈现的 选择 std::initializer_list
构造函数而不是直接匹配的行为是明确定义的并且符合标准。
此问题在 Scott Meyers Effective Modern C++ book 中有详细描述
第 7 项:
If, however, one or more constructors declare a parameter of type
std::initializer_list
, calls using the braced initialization syntax
strongly prefer the overloads taking std::initializer_lists. Strongly.
If there’s any way for compilers to construe a call using a braced
initializer to be to a constructor taking a std::initializer_list
,
compilers will employ that interpretation.
他还介绍了这个问题的一些边缘案例,我强烈建议阅读。
让我们考虑以下问题:
#include <iostream>
#include <initializer_list>
class Foo {
public:
Foo(int) {
std::cout << "with int\n";
}
};
int main() {
Foo a{10}; // new style initialization
Foo b(20); // old style initialization
}
在 运行 它打印:
with int
with int
一切顺利。现在由于新的要求,我添加了一个构造函数,它接受一个初始化列表。
Foo(std::initializer_list<int>) {
std::cout << "with initializer list\n";
}
现在打印:
with initializer list
with int
所以我的旧代码 Foo a{10}
悄无声息地被破坏了。 a
应该用 int
初始化。
我了解语言语法将 {10}
视为包含一项的列表。但是我怎样才能防止旧代码的这种无声破坏?
- 是否有任何编译器选项会在这种情况下向我们发出警告?因为这将是特定于编译器的,所以我对 gcc 最感兴趣。我已经试过了
-Wall -Wextra
. - 如果没有这样的选项,那么我们是否总是需要使用旧式构造,即使用
()
Foo b(20)
,对于其他构造函数,只有在我们真正想要的时候才使用{}
初始化列表?
我找不到这样的选项,所以显然在这种情况下,您应该为具有 initializer_list 构造函数的 类 使用括号,并为所有其他 类 使用统一初始化作为你希望。
可以在此答案和评论中找到一些有用的见解:
没有编译器警告,也永远不会有。警告代码做一些常见的事情是没有意义的,比如
std::vector vec{1};
请记住,编译器只会警告 真正 不需要的东西,比如未定义的行为。它无法知道在上面的定义中,您打算调用带有大小参数的构造函数。据它所知,您实际上想要 有一个只有一个元素的向量!它无法读懂你的想法:)
你第二个问题的答案基本是肯定的。你总是可以添加一个像 struct {} dummy;
这样的虚拟参数来避免使用带有初始化列表的构造函数,但实际上,唯一相同的解决方案只是使用圆括号而不是大括号(或者不要突然破坏接口)。
如果要更改所有使用列表初始化的代码,可以删除初始化列表构造函数,将它们更改为大括号,然后然后正确实现构造函数。我会认为这样的改变是一个破坏性的改变,并适当地处理它。另一个想法是事先提出初始化列表用例,并立即实施。
在这些情况下不可能生成任何警告,因为呈现的 选择 std::initializer_list
构造函数而不是直接匹配的行为是明确定义的并且符合标准。
此问题在 Scott Meyers Effective Modern C++ book 中有详细描述 第 7 项:
If, however, one or more constructors declare a parameter of type
std::initializer_list
, calls using the braced initialization syntax strongly prefer the overloads taking std::initializer_lists. Strongly. If there’s any way for compilers to construe a call using a braced initializer to be to a constructor taking astd::initializer_list
, compilers will employ that interpretation.
他还介绍了这个问题的一些边缘案例,我强烈建议阅读。