C++是一个编译器会替你在背后做很多事情的语言,包括模板实例化,按需要创造隐式的构造函数,默认构造你没有显式构造的成员,按需进行隐式转换和饮食构造等等,如果没有彻底了解清楚,就容易被这些编译器背后做好的事情坑到,这个系列文章就来总结我在写C++时遇到的各种坑。
所谓隐式调用和默认实现的构造函数,当你写一个赋值语句的时候,编译器会首先检查两个类型又没有直接实现的赋值函数,然后检查赋值左右的类型是否能做隐式转换和构造,转换或者构造好之后,再尝试进行拷贝或移动赋值。这时候,坑点来了,如果你有这么样的一个类:
struct Token
{
int label;
string content;
Token(int _label = -1, string _content = "")
: label(_label)
, content(_content)
{}
};
看它的构造函数,每一个参数都有默认参数,这个东西是会被编译器当成默认构造函数的,并且这个构造函数不会被视为用户自己实现的构造函数,所以编译器依然会按需自动生成其他的[拷贝|移动][构造|赋值]函数,所以这时候坑点来了,以下的语句是合法的:
Token label(1, "hello"); label = 2; //这tm是合法的!!!
label = 2,这条语句会让编译器隐式调用Token的构造函数用2构造一个Token,参数的_content采用默认值“”,然后又调用隐式生成的移动赋值(move assignment)函数,进行赋值。而且毫无警告发生,这样写可能还比较明显,容易发现问题,如果代码复杂起来,被坑的可能性就大大提高了,我自己的tokenizer generator就是有一个bug坑在了这个地方,幸亏IDE能识别出运算符重载以及显示鼠标指向的变量的值,让我很快发现了这个bug,不然又不知道要debug到猴年马月去了。这个问题还说明了使用一个牛逼的IDE的重要性,倘若是用VC6……呵呵呵呵呵。
话说我该把这个构造函数声明为explicit的,忘了