一、注解是什么
其实中这里分析注解的原因是和反射有些关系的,当然实现注解的手段可能有很多种,但一般来说,注解实现都和反射或多或少有着关系。那么什么是注解?注解就是对类或方法等的元数据描述。所以注解就是一种元数据,它是一种对代码的特殊标记。
注解可以有两种方式来应用,一种是在编译期进行扫描;另外一个是在运行期使用反射的方式来获取相关的注解信息。所以从这两点来分析,c++目前来看,只能在编译期进行注解的处理。在运行期利用反射,至少标准目前还不支持,都是一些框架自己实现的。
二、c++11以上的注解
在早期的c++标准中,对注解可以说是没有支持的必要。毕竟玩儿c++的可都不是一般的程序员,但再高大上的东西,总有一天要“飞入寻常百姓家”,而且注解这个东西确实是对开发者有着不小的作用,所以从c++11开始,注解就慢慢上来了。不过在c++中可能不叫注解,叫属性(Attribute)。
Syntax
[[ attribute-list ]] (since C++11)
[[ using attribute-namespace : attribute-list ]] (since C++17)
where attribute-list is a comma-separated sequence of zero or more attributes (possibly ending with an ellipsis ... indicating a pack expansion)identifier (1)
attribute-namespace :: identifier (2)
identifier ( argument-list ) (3)
attribute-namespace :: identifier ( argument-list ) (4)
1) Simple attribute, such as [[noreturn]].
2) Attribute with a namespace, such as [[gnu::unused]].
3) Attribute with arguments, such as [[deprecated("because")]].
4) Attribute with both a namespace and an argument list.
其实在早期的开发者中,也可以看到一些编译器厂商自己增加的属性,如GNU 的语言扩展 _attribute_((…)),微软的语言扩展 __declspec() 等,一般情况下,属性可以应用于c++中所有的位置,包括类型、变量、函数、代码块甚至整个单元等等。不过这里强调的注解可能更货币于函数和类型。也就是类似于:
[[attribute]] types/functions
在c++11以后定义了下列几个标准的属性:
C++ 标准仅定义下列属性。[[noreturn]](C++11 起) 指示函数不返回
[[carries_dependency]](C++11 起) 指示释放消费 std::memory_order 中的依赖链传入和传出该函数。
[[deprecated]](C++14 起)
[[deprecated("原因")]](C++14 起) 指示允许使用声明有此属性的名称或实体,但因 原因 而不鼓励使用。
[[fallthrough]](C++17 起) 指示从前一 case 标号直落是有意的,而在发生直落时给出警告的编译器不应该为此诊断。
[[nodiscard]](C++17 起)
[[nodiscard("原因")]](C++20 起) 鼓励编译器在返回值被舍弃时发布警告。
[[maybe_unused]](C++17 起) 压制编译器在未使用实体上的警告,若存在。
[[likely]](C++20 起)
[[unlikely]](C++20 起) 指示编译器应该针对通过某语句的执行路径比任何其他执行路径更可能或更不可能的情况进行优化。
[[no_unique_address]](C++20 起) 指示非静态数据成员不需要拥有不同于其类的所有其他非静态数据成员的地址。
[[assume]](C++23 起) 指出某个表达式在某个地方始终会求值为 true。
[[optimize_for_synchronized]](TM TS) 指示应该针对来自 synchronized 语句的调用来优化该函数定义
对于支持自定义属性,目前标准还是比较模糊,反正c++有一条,如果不合适,结果就是未知,自己考虑清楚即可。
三、例程
先看几个cppreference上的例程:
[[ noreturn ]] void f() {throw "error";// OK
}void q [[ noreturn ]] (int i) {// 若以 <= 0 的参数调用则行为未定义if (i > 0) {throw "positive";}
}// void h() [[noreturn]]; // 错误:属性应用到 h 的函数类型,而非 h 自身
象一些系统调用的API函数,都可以增加这个属性来防止未知情况。
#include <iostream>[[deprecated]]
void TriassicPeriod() {std::clog << "Triassic Period: [251.9 - 208.5] million years ago.\n";
}[[deprecated("Use NeogenePeriod() instead.")]]
void JurassicPeriod() {std::clog << "Jurassic Period: [201.3 - 152.1] million years ago.\n";
}[[deprecated("Use calcSomethingDifferently(int).")]]
int calcSomething(int x) {return x * 2;
}int main()
{TriassicPeriod();JurassicPeriod();
}
再看一个前面曾经提到过的:
#include <iostream>int f(int i)
{if (i < 0) [[unlikely]] {return 0;}return 1;
}int main()
{std::cout << f(-1) << std::endl;std::cout << f(1) << std::endl;
}
最后再看一个常用的:
void f(int n)
{void g(), h(), i();switch (n){case 1:case 2:g();[[fallthrough]];case 3: // 直落时不警告h();case 4: // 编译器可在发生直落时警告if (n < 3){i();[[fallthrough]]; // OK}else{return;}case 5:while (false){[[fallthrough]]; // 非良构:下一语句不是同一迭代的一部分}case 6:[[fallthrough]]; // 非良构:没有后继的 case 或 default 标号}
}
其实很多小的技术细节,往往会被开发者忽略,但当某个时刻看到某些人应用的非常巧妙时,又击节叹服。其实这说白了还是对整个体系掌握的不够深入,等深入掌握了,某些细节其实就是习惯的事情了。
四、总结
其实谈注解并没有多少太多的新奇的技术,在其它高级语言,特别是搞Java的同学中,这个太多了,现在几乎是普遍了。所以c++也在跟进,好的东西,其实是普遍受欢迎的,正如美是没有国界的。但c++固有的属性使其的发展不可能一下子就对注解之类的技术支持非常完美。但是只要开了头,后面就会越来越好。
这里把这个注解拿出来,就是和反射在一起分析,让大家有一个更深刻的认识。对开发者来说简单的事情,可能对底层开发支持人员,就是一个新技术的应用。每个次语言标准的进步,其实就是很多基础开发人员的努力,希望有一天,我们也能坐到这个位置,为普通开发者,提供技术支持。