C++踩过的坑
noexcept函数抛出异常
原本设计函数的时候明确该函数不会抛出异常,所以给函数加上了noexcept属性,但修改代码之后需要抛出异常,比如:
void func() noexcept {
...
throw except();
}
即使像这种很明显的语法矛盾,有的编译器的版本并不会发出警告,于是进行测试和实际代码运行的时候在外层使用try catch
块总是捕获不到异常,这一点可能会浪费你很多时间。
NULL匹配指针类型还是整型
在C++的世界中字面值0用来表示空指针,所以0可以当作所有指针类型的字面值。为了让语义更明确引入了NULL宏定义:
#undef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
这说明了在C++中,NULL是被替换为0的(在一些实现中NULL可能被定义为0L),这是因为C++不允许void指针隐式转换为其它类型指针,但是允许0作为各指针类型的字面值常量。
下面的场景可能会报二义性的问题:
void func(char *str) {
printf("char *");
}
void func(int n) {
printf("int");
}
int main() {
func(NULL);
return 0;
}
这里假设NULL
被定义为OL
,所以long
型转换为int
和char*
存在二义性。
most vexing parse
C++'s most vexing parse 是 Scott Meyers 在其名著《Effective STL》中创造的一个术语。
初始化对象的时候会被当作函数的声明:
struct B {
explicit B(int x){}
};
struct A {
A (B const& b){}
void doSomething(){}
};
int main() {
int x = 42;
A a(B(x));
a.doSomething();
}
这里A a(B(x));
会被当作函数的声明,返回类型是A
,参数类型是B
,x
是参数名。
在C++11中,可以用统一初始化语法,A a(B{x})
来消除二义性。
printf类型不匹配
首先需要知道,当变量作为不定参数列表的参数时,char
,short
等类型会默认提升为int
类型,float
类型会默认提升到double
类型,当printf
函数中描述符和参数类型不匹配时,可能会造成读取非法内存:
int n = 0;
printf("%lld", n);
上述代码会读取非法内存,有的运行环境会直接报segment fault
错误停止程序运行,但对于标准来说是未定义行为,需要避免。
全局变量初始化/销毁顺序
对于出现在同一个编译单元内的全局变量来说,它们初始化的顺序与他们声明的顺序是一致的(销毁的顺序则反过来),而对于不同编译单元间的全局变量,c++ 标准并没有明确规定它们之间的初始化(销毁)顺序应该怎样,因此实现上完全由编译器自己决定,一个比较普遍的认识是:不同编译单元间的全局变量的初始化顺序是不固定的,哪怕对同一个编译器,同一份代码来说,任意两次编译的结果都有可能不一样。