前言
在class中带容量初始化vector时,发生报错,Expected parameter declarator
、Expected ')'
,我寻思着我括号都敲对了啊,怎么就出问题了?加上初始值,还是报错,然后准备删掉初始值和size时,才发现我好像声明了个函数……真坑,规范编程太重要了。
class Solution {
private:
// vector<int> v1(10); // 错误
// vector<int> v1(10, 0); // 错误
// vector<int> v1(); // 仔细一看,这不是声明了个空参返回vector的v1函数吗!所以前两行错误的原因是编译器识别v1为函数而10又不是任何一种type
[...]
}
有个术语专门指代这种情况:most vexing parse,即最令人烦恼的解析,是C++中一种反直觉的二义解析形式。简单说就是,在编译器无法区分某语句是初始化对象参数还是声明一个函数时指定参数类型时,就会将该行解释为函数声明。除了初始化vector以外还有好多情况会出现这种MVP。
先说结论
那么如何这样带参数初始化呢?
在C++11以后,有了统一初始化(Uniform initialization),可以用大括号完成我们想要的效果,此外还有第二种赋值初始化。
class Solution {
private:
vector<int> v1{vector<int>(10, 0)}; // 第二个参数缺省为0
vector<int> v2 = vector<int>(10, 0); // 第二个参数缺省为0
[...]
}
需要注意:
vector<int> v1{10}
是按元素初始化,只会初始化一个元素10;
而在C++11之前,就没有这种初始化方式了,我们必须老老实实在构造函数里面初始化,可以用初始化列表或者vector的resize函数:
// 方法一:
class Solution {
private:
vector<int> v1;
[...]
public:
Solution() : v1(10, 0) {} // 在构造函数初始化列表里面初始化
}
// 方法二:
class Solution {
private:
vector<int> v1;
[...]
public:
Solution() {
v1.resize(10, 0); // 在构造函数中用resize函数
}
}
从兼容性来说,还是方法二最合适。
细说一下MVP
好,我们不能只满足于解决了初始化问题,来细看一下这个MVP到底是个啥,以及如何避免。
看Wiki给的一个例子:
struct Timer {};
struct TimeKeeper {
explicit TimeKeeper(Timer t);
int get_time();
};
int main() {
TimeKeeper time_keeper(Timer()); // 这里有歧义
return time_keeper.get_time();
}
在代码中标注一行中的time_keeper
可以有两种解释:
-
一个
TimeKeeper
类型的变量,用类Timer
的默认构造函数初始化;这也是我们想实现的 -
一个函数
time_keeper
,返回值是TimeKeeper
类型,有一个函数参数,等价为一个指针参数,该指针指向Timer
的匿名构造函数。(见注释)注:根据C++类型退化规则,作为参数声明的函数等价于一个指向同类型函数的指针。参见C++函数对象的实例。
C++标准中采取第二种解释,将这种MVP情况统一解释为函数,导致出错。
如何避免MVP
自C++11以后,首选方法是使用统一初始化:
// 以下均可
TimeKeeper time_keeper(Timer{});
TimeKeeper time_keeper{Timer()};
TimeKeeper time_keeper{Timer{}};
TimeKeeper time_keeper( {}); // 甚至允许完全省略类型名称
TimeKeeper time_keeper{ {}};
在C++11之前,强制获得预期解释的常用手段是使用额外括号或拷贝初始化,后一种写法更常见,而且可能被编译器优化,C++17开始,可以保证这种优化。
TimeKeeper time_keeper( (Timer()) ); // 额外括号
TimeKeeper time_keeper = TimeKeeper(Timer()); // 拷贝初始化
结语
总之结论就是,留意这种MVP,并在coding中避免MVP,对我自己来说以后除了写算法题还是老老实实在构造函数的初始化列表里面初始化吧。
参考资料: