编译阶段
- 编译是预处理之后的阶段,输入是通过预处理之后的C++源码,输出是二进制可执行文件。
- 编译阶段看到的都是 C++语法实体,比如说typedef ,using ,template , struct/class 这些关键字定义的类型,而不是运行阶段的变量。
比如说,让编译器去递归计算斐波那契数列
template<int N>
struct fib // 递归计算斐波那契数列
{
static const int value = fib<N - 1>::value + fib<N - 2>::value;
};
template<>
struct fib<0> // 模板特化计算fib<0>
{
static const int value = 1;
};
template<>
struct fib<1> // 模板特化计算fib<1>
{
static const int value = 1; }; // 调用后输出2,3,5,8
cout << fib<2>::value << endl;
cout << fib<3>::value << endl;
cout << fib<4>::value << endl;
cout << fib<5>::value << endl
}
这里不讨论上面那种太过烧脑
的问题,在这里介绍两种比较容易理解的编译阶段技巧 : 属性和静态断言
属性
控制编译器的指令,在GCC里面是 “__ attribute __”,在C++11中有一个正式的名字叫属性
,可以理解为给变量,函数,类去贴一个标签,方便编译器去进行识别。
属性
是通过方括号"[[…]]".来进行表示的
例如
[[noreturn]] // 属性标签
int func(bool flag) // 函数绝不会返回任何值
{
throw std::runtime_error("XXX");
}
在C++11 中只有两个属性 “noreturn”和“carries_dependency”
没有什么用处,在C++14中增加了"deprecated"用来标记不推荐使用的变量,函数或者类。
deprecated
类标记这个类/变量已经被费用,可以通过添加一些说明文字来表示
用到这个被标记的程序,会报出警告。
其中"属性"也支持非标准扩展,循序类似于名字空间的方式使用编译器自己的一些"非官方"属性,比如, GCC的属性都在 “gnu::” 中
deprecated:与 C++14 相同,但可以用在 C++11 里。
unused:用于变量、类型、函数等,表示虽然暂时不用,但最好保留着,因为将来可能 会用。
constructor:函数会在 main()函数之前执行,效果有点像是全局对象的构造函数。
destructor:函数会在 main() 函数结束之后执行,有点像是全局对象的析构函数。
always_inline:要求编译器强制内联函数,作用比 inline 关键字更强。 hot:标记“热点”函数,要求编译器更积极地优化。
静态断言(static_assert)
我们可以类比于assert , 它可以看作在编译阶段里的断言,如果值是false , 就会报错,编译失败
我们可以在之前开始的斐波拉切数列计算函数,利用静态断言来保证模板参数必须大于等于0
template<int N>
struct fib
{
static_assert(N >= 0, "N >= 0");
static const int value = fib<N - 1>::value + fib<N - 2>::value;
};
这里需要注意的是,static_assert 只是运行在编译阶段,看到编译的常数和类型,看不到运行时候的变量,指针,内存数据。