了解何时通过 class 构造函数在 () 和 {} 初始化中进行检查
Understanding when checks happen in () and {} initization through class constructors
据我了解,{}
是一种初始化变量的方法,与其他方法相比具有一些“安全”优势,例如禁止缩小:
int some_int_a = 1.2; // narrows
int some_int_b (1.2); // narrows
int some_int_c {1.2}; // does NOT compile, cannot narrow
到目前为止一切顺利。我最近发现但我不完全理解的是,这种检查何时发生?例如,在下面的代码中:
#include <iostream>
class ExClass{
private:
const int i;
public:
ExClass(int i=0): i{i} {
// needed even if empty
}
void print(void){
std::cout << "const int i = " << i << std::endl;
}
};
int main(void)
{
ExClass ex_a (2.3); // narrows! this was surprising to me, I (probably naively)
// expected i{i} in the constructor to forbid this.
ex_a.print();
ExClass ex_b {2.3}; // does not compile
ex_b.print();
return 0;
}
我想这意味着在 ex_a
的情况下,首先使用收缩转换完全创建中间 int
,然后这个中间 int
用于括号初始化i
在构造函数中。而在第二种情况下,中间 int
不能用有冲突的输入进行括号初始化,对吗?
有没有办法以没有“中间”缩小的方式编写内容,以便 class 括号初始化检测到错误输入?
调试器可能会很快向您显示!
看看你在做什么:
if(ExClass(next) == false || opening_brackets_stack.empty() == false)
当您尝试将它与右括号进行比较时,ExClass 的类型是什么
提示:想一想何时创建 Bracket 的每个新实例。都是括号吗?
都是开括号吗?
这里发生的是构造函数:
ExClass(int i=0): i{i}
采用 int
参数,并在此处缩小:
ExClass ex_a (2.3);
很好。一旦进入构造函数,i
就是一个 int
(参数,而不是成员)。因此 i{i}
没有变窄(它是 int
到 int
)。当您将构造函数更改为:
时,您会得到 the error
ExClass(double i=0): i{i} { }
因为现在 i{i}
确实在变窄。
请注意,gcc 仅使用默认设置发出 a warning,需要 -pedantic-errors
才能将其识别为错误。感谢 Remy 指出它实际上是一个错误,而不是警告。
大括号初始化 (C++11 起) 防止变窄。正如 cppreference 在 缩小转化率 中所述:
list-initialization limits the allowed implicit conversions by
prohibiting the following:
- conversion from a floating-point type to an integer type
- conversion from a long double to double or to float and conversion from double to float, except where the source is a constant expression
and overflow does not occur
- conversion from an integer type to a floating-point type, except where the source is a constant expression whose value can be stored
exactly in the target type
- conversion from integer or unscoped enumeration type to integer type that cannot represent all values of the original, except where
source is a constant expression whose value can be stored exactly in
the target type
- conversion from a pointer type or pointer-to-member type to bool (since C++20)
因此,比起替代方案,更喜欢大括号初始化。
Is there a way to write things in such a way that there is no "intermediate" narrowing, so that the class bracket initialization detects the faulty input?
你可以让构造函数接受一个模板参数,这样它就接受调用者传入的任何类型,例如:
#include <iostream>
class ExClass{
private:
const int i;
public:
template<typename T>
ExClass(T i=T{}): i{i} {
// needed even if empty
}
void print(void){
std::cout << "const int i = " << i << std::endl;
}
};
int main(void)
{
ExClass ex_a (2.3); // should not compile!
ex_a.print();
ExClass ex_b {2.3}; // should not compile!
ex_b.print();
return 0;
}
不幸的是,GCC 默认允许缩小范围,发出 警告 而不是错误(使用 -pedantic-errors
强制错误):
warning: narrowing conversion of 'i' from 'double' to 'int' [-Wnarrowing]
Clang 发出错误,但是:
error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]
据我了解,{}
是一种初始化变量的方法,与其他方法相比具有一些“安全”优势,例如禁止缩小:
int some_int_a = 1.2; // narrows
int some_int_b (1.2); // narrows
int some_int_c {1.2}; // does NOT compile, cannot narrow
到目前为止一切顺利。我最近发现但我不完全理解的是,这种检查何时发生?例如,在下面的代码中:
#include <iostream>
class ExClass{
private:
const int i;
public:
ExClass(int i=0): i{i} {
// needed even if empty
}
void print(void){
std::cout << "const int i = " << i << std::endl;
}
};
int main(void)
{
ExClass ex_a (2.3); // narrows! this was surprising to me, I (probably naively)
// expected i{i} in the constructor to forbid this.
ex_a.print();
ExClass ex_b {2.3}; // does not compile
ex_b.print();
return 0;
}
我想这意味着在 ex_a
的情况下,首先使用收缩转换完全创建中间 int
,然后这个中间 int
用于括号初始化i
在构造函数中。而在第二种情况下,中间 int
不能用有冲突的输入进行括号初始化,对吗?
有没有办法以没有“中间”缩小的方式编写内容,以便 class 括号初始化检测到错误输入?
调试器可能会很快向您显示! 看看你在做什么: if(ExClass(next) == false || opening_brackets_stack.empty() == false)
当您尝试将它与右括号进行比较时,ExClass 的类型是什么
提示:想一想何时创建 Bracket 的每个新实例。都是括号吗? 都是开括号吗?
这里发生的是构造函数:
ExClass(int i=0): i{i}
采用 int
参数,并在此处缩小:
ExClass ex_a (2.3);
很好。一旦进入构造函数,i
就是一个 int
(参数,而不是成员)。因此 i{i}
没有变窄(它是 int
到 int
)。当您将构造函数更改为:
ExClass(double i=0): i{i} { }
因为现在 i{i}
确实在变窄。
请注意,gcc 仅使用默认设置发出 a warning,需要 -pedantic-errors
才能将其识别为错误。感谢 Remy 指出它实际上是一个错误,而不是警告。
大括号初始化 (C++11 起) 防止变窄。正如 cppreference 在 缩小转化率 中所述:
list-initialization limits the allowed implicit conversions by prohibiting the following:
- conversion from a floating-point type to an integer type
- conversion from a long double to double or to float and conversion from double to float, except where the source is a constant expression and overflow does not occur
- conversion from an integer type to a floating-point type, except where the source is a constant expression whose value can be stored exactly in the target type
- conversion from integer or unscoped enumeration type to integer type that cannot represent all values of the original, except where source is a constant expression whose value can be stored exactly in the target type
- conversion from a pointer type or pointer-to-member type to bool (since C++20)
因此,比起替代方案,更喜欢大括号初始化。
Is there a way to write things in such a way that there is no "intermediate" narrowing, so that the class bracket initialization detects the faulty input?
你可以让构造函数接受一个模板参数,这样它就接受调用者传入的任何类型,例如:
#include <iostream>
class ExClass{
private:
const int i;
public:
template<typename T>
ExClass(T i=T{}): i{i} {
// needed even if empty
}
void print(void){
std::cout << "const int i = " << i << std::endl;
}
};
int main(void)
{
ExClass ex_a (2.3); // should not compile!
ex_a.print();
ExClass ex_b {2.3}; // should not compile!
ex_b.print();
return 0;
}
不幸的是,GCC 默认允许缩小范围,发出 警告 而不是错误(使用 -pedantic-errors
强制错误):
warning: narrowing conversion of 'i' from 'double' to 'int' [-Wnarrowing]
Clang 发出错误,但是:
error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]