“编译阶段”的目标是生成计算机可识别的机器码。
编译是预处理之后的阶段,它的输入是(经过预处理的)C++ 源码,输出是二进制可执行文件(也可能是汇编文件、动态库或者静态库)。这个处理动作就是由编译器来执行的。
有没有用来控制编译器的“编译指令”呢?如果有这么一个东西,让程序员来手动指示编译器这里该如何做、那里该如何做,就有可能会生成更高效的代码。
到了 C++11,标准委员会终于认识到了“编译指令”的好处,于是就把“民间”用法升级为“官方版本”,起了个正式的名字叫“属性”。你可以把它理解为给变量、函数、类等“贴”上一个编译阶段的“标签”,方便编译器识别处理。
“属性”没有新增关键字,而是用两对方括号的形式“[[…]]”,方括号的中间就是属性标签,注意属性的编写规范,作用范围。
[[noreturn]] // 属性标签
int func(bool flag) // 函数绝不会返回任何值
{
throw std::runtime_error("XXX");
}
// 声明变量暂不使用,不是错误
[[gnu::unused]]
int nouse;
// C++14 or later
// 当然,程序还是能够正常编译的,但这种强制的警告形式会“提醒”用户旧接口已经被废弃了,应该尽快迁移到新接口。
[[deprecated("deadline:2020-12-31")]]
int old_func();
下面我就列出几个比较有用的(全部属性可参考GCC 文档),GCC 的属性都在 “gnu::” 里。
-
deprecated:与 C++14 相同,但可以用在 C++11 里。
-
unused:用于变量、类型、函数等,表示虽然暂时不用,但最好保留着,因为将来可能会用。
-
constructor:函数会在 main() 函数之前执行,效果有点像是全局对象的构造函数。
-
destructor:函数会在 main() 函数结束之后执行,有点像是全局对象的析构函数。
-
always_inline:要求编译器强制内联函数,作用比 inline 关键字更强。
-
hot:标记“热点”函数,要求编译器更积极地优化。
静态断言(static_assert)
static_assert 可以在编译阶段定义各种前置条件,充分利用 C++ 静态类型语言的优势,让编译器执行各种检查,避免把隐患带到运行阶段。
“静态断言”,static_assert (它是一个关键字),而不是宏。因为它只在编译时生效,运行阶段看不见,所以是“静态”的。它是编译阶段里检测各种条件的“断言”,编译器看到 static_assert 也会计算表达式的值,如果值是 false,就会报错,导致编译失败。
static_assert(
sizeof(long) >= 8, "must run on x64");
static_assert(
sizeof(int) == 4, "int must be 32bit");
//下面的代码想检查空指针,由于变量只能在运行阶段出现,而在编译阶段不存在,所以静态断言无法处理。
char* p = nullptr;
static_assert(p == nullptr, "some error."); // 错误用法
在泛型编程的时候,怎么检查模板类型呢?比如说,断言是整数而不是浮点数、断言是指针而不是引用、断言类型可拷贝可移动等等?
想要更好地发挥静态断言的威力,还要配合标准库里的“type_traits”,它提供了对应这些概念的各种编译期“函数”。
// 假设 T 是一个模板参数,即 template<typename T>
static_assert(
is_integral<T>::value, "int");
static_assert(
is_pointer<T>::value, "ptr");
static_assert(
is_default_constructible<T>::value, "constructible");
网友评论