为什么成员初始值设定项不能使用括号?
Why can't member initializers use parentheses?
例如,我不能这样写:
class A
{
vector<int> v(12, 1);
};
我只能这样写:
class A
{
vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);
};
为什么这两种声明语法有区别?
这个选择背后的理由在非静态数据成员初始化式:
的相关建议中明确提到。Kona中关于标识符作用域的问题:
在2007年9月Kona会议的核心工作组讨论中,出现了一个关于初始化式中标识符范围的问题。我们是否希望允许类作用域具有正向查找的可能性;还是要求初始化式在解析时定义良好?
期望:
类作用域查找的动机是,我们希望能够在不显著改变语义(模直接初始化vs复制初始化)的情况下,在非静态数据成员的初始化项中放入任何可以放入memm初始化项的内容:
int x();
struct S {
int i;
S() : i(x()) {} // currently well-formed, uses S::x()
// ...
static int x();
};
struct T {
int i = x(); // should use T::x(), ::x() would be a surprise
// ...
static int x();
};
问题1:
不幸的是,这使得" (expression-list) "形式的初始化式在解析声明时具有歧义性:
struct S {
int i(x); // data member with initializer
// ...
static int x;
};
struct T {
int i(x); // member function declaration
// ...
typedef int x;
};
一个可能的解决方案是依靠现有的规则,如果一个声明可以是一个对象或一个函数,那么它就是一个函数:
struct S {
int i(j); // ill-formed...parsed as a member function,
// type j looked up but not found
// ...
static int j;
};
一个类似的解决方案是应用另一个现有的规则,目前只在模板中使用,如果T可以是类型或其他东西,那么它就是其他东西;如果我们真的想要一个类型,我们可以使用typename:
struct S {
int i(x); // unabmiguously a data member
int j(typename y); // unabmiguously a member function
};
这两种解决方案引入的微妙之处很可能被许多用户误解(comp.lang.c++上关于为什么在块作用域"int i();"没有声明默认初始化的int的许多问题就是证据)。
本文提出的解决方案是只允许"= initializer-子句"answers"{initializer-list}"形式的初始化式。这解决了大多数情况下的歧义问题,例如:
HashingFunction hash_algorithm{"MD5"};
这里不能使用=形式,因为HasningFunction的构造函数是显式的。在特别棘手的情况下,一个类型可能不得不被提及两次。考虑:
vector<int> x = 3; // error: the constructor taking an int is explicit
vector<int> x(3); // three elements default-initialized
vector<int> x{3}; // one element with the value 3
在这种情况下,我们必须使用适当的表示法在两个选项之间进行选择:
vector<int> x = vector<int>(3); // rather than vector<int> x(3);
vector<int> x{3}; // one element with the value 3
问题2:
另一个问题是,因为我们不建议修改初始化静态数据成员的规则,所以添加static关键字可能会使格式良好的初始化器变成格式不良的:
struct S {
const int i = f(); // well-formed with forward lookup
static const int j = f(); // always ill-formed for statics
// ...
constexpr static int f() { return 0; }
};
问题3:
第三个问题是类作用域查找可能把编译时错误变成运行时错误:
struct S {
int i = j; // ill-formed without forward lookup, undefined behavior with
int j = 3;
};
(除非被编译器捕获,否则i可能被j的未定义值初始化。)
建议:
CWG在Kona进行了6比3的民意调查,支持类作用域查找;这就是本文所建议的,非静态数据成员的初始化器仅限于"= initializer-子句"answers"{initializer-list}"形式。
我们相信:
问题1:这个问题不会发生,因为我们没有提出()符号。=和{}初始化项符号没有这个问题。
问题2:添加static关键字会产生许多不同,这是其中最小的。
问题3:这不是一个新问题,但与构造函数初始化项中已经存在的初始化顺序问题相同。
一个可能的原因是允许括号将使我们很快回到最令人烦恼的解析。考虑下面两种类型:
struct foo {};
struct bar
{
bar(foo const&) {}
};
现在,您有一个要初始化的bar
类型的数据成员,因此将其定义为
struct A
{
bar B(foo());
};
但是你上面所做的是声明一个名为B
的函数,它返回一个bar
对象的值,并接受一个参数,这个参数是一个签名为foo()
的函数(返回一个foo
,不接受任何参数)。
从StackOverflow上关于这个问题的提问数量和频率来看,这是大多数c++程序员感到惊讶和不直观的事情。添加新的大括号或等号初始化器语法是避免这种歧义并从头开始的一个机会,这可能是c++委员会选择这样做的原因。
bar B{foo{}};
bar B = foo();
如上两行声明了一个名为B
的bar
类型的对象。
除了上面的猜测,我想指出你在上面的例子中做了两件截然不同的事情。
vector<int> v1{ 12, 1 };
vector<int> v2 = vector<int>(12, 1);
第一行将v1
初始化为包含两个元素12
和1
的vector。第二个函数创建一个包含12
元素的向量v2
,每个元素初始化为1
。
注意这个规则——如果一个类型定义了一个接受initializer_list<T>
的构造函数,那么当该类型的初始化项是带括号的init-list时,总是首先考虑该构造函数。只有当使用initializer_list
的构造函数不可行时,才会考虑其他构造函数。
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 为什么我不能在一个类的不同行中声明和定义成员变量?
- 为什么派生类的好友不能使用受保护的成员?
- 为什么我不能在返回 const 的布尔函数中为类成员变量赋值?C++
- 为什么"具有常量成员的结构"类型的指针不能指向"具有非常量成员的结构"?
- 为什么不能直接引用作用域枚举类成员,而不能为无作用域枚举生成类成员?
- 不能制作需要布尔成员函数的C++20概念
- C++为什么类成员函数不能重新声明,但普通函数可以
- 为什么我不能像这样在静态成员函数中调用静态成员变量?
- 为什么我们不能在C++中初始化类的成员变量
- 成员函数不能为集合迭代器和const_iterator的输入重载(但可以为其他 STL 迭代器重载)
- 为什么静态数据成员不能在c++11中的类中初始化
- 派生类不能用另一个基类的成员重载基类中的私有成员
- 为什么相应成员不能正确访问成员函数指针
- using声明不能引用类成员
- 为什么不能指向指针,在没有强制转换的情况下访问结构成员?
- 为什么不能提升::hana::overload_t成为类的成员
- 为什么我不能将 int 分配给结构成员的联合成员?
- 为什么我不能只用前向声明 c++ 声明类的静态成员?
- 为什么不能使用"( )"为类的非静态数据成员提供默认值?