尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

RIOT C++ 编码规范完整指南:从风格约定到源码实践

RIOT C++ 编码规范完整指南:从风格约定到源码实践 RIOT C 编码规范完整指南从风格约定到源码实践【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT本指南系统梳理 RIOTThe friendly OS for IoT在仓库根目录 CODING_CONVENTIONS_C.md 中正式发布的 C 编码规范其 Starlight 文档模板见 doc/starlight/templates/CODING_CONVENTIONS_CPP.template.md并对照仓库内真实 C 源码给出可验证的落地示例。读完本文你将掌握 RIOT 的 C 命名、头文件组织、换行规则、模板元编程格式与注释约定能够写出风格统一、可被 uncrustify-riot.cfg 自动格式化、易于其他贡献者 review 的 RIOT C 代码。规范定位与适用范围RIOT 的 C 编码规范建立在C 语言编码规范的基础之上文档开篇即明确You should check out the C Conventions as some section still apply (Documentation, Git, Travis)即文档规范、Git 提交规范、CITravis/Murdock要求等通用章节同时适用于 C 与 C。C 语言规范全文位于仓库根目录 CODING_CONVENTIONS.md。在风格取向上RIOT 的 C 规范主要参考了Google C Style Guide与C 标准库的惯用写法并在此基础上吸收了CAFC Actor Framework的编码风格最终形成一套兼顾可读性与模板元编程可维护性的自家约定。其核心目标可以概括为在 C 语法噪音尤其是模板语法不可避免的前提下通过统一的格式约定让代码在视觉上可被预测降低 review 与维护成本。快速示例Example for the Impatient规范先给出了一对完整可编译的头文件/实现文件示例几乎涵盖了后面所有章节的规则适合急性子读者直接对照模仿。头文件示例约定路径module/riot/example/my_class.hpp// module/riot/example/my_class.hpp #ifndef RIOT_EXAMPLE_MY_CLASS_HPP #define RIOT_EXAMPLE_MY_CLASS_HPP #include string // use // for regular comments and /// for doxygen namespace riot { namespace example { /// This class is only being used as style guide example. class my_class { public: /// Brief description. More description. Note that RIOT uses the /// JavaDoc-style autobrief option, i.e., everything up until the /// first dot is the brief description. my_class(); /// Destructs my_class. Please use Markdown in comments. ~my_class(); // suppress redundant return if you start the brief description with Returns /// Returns the name of this instance. inline const std::string name() const { return name_; } /// Sets the name of this instance. inline void name(const std::string new_name) { name_ new_name; } /// Prints the name to STDIN. void print_name() const; /// Does something (maybe). void do_something(); /// Does something else. void do_something_else(); private: std::string name_; }; } // namespace example } // namespace riot #endif // RIOT_EXAMPLE_MY_CLASS_HPP实现文件示例my_module/my_class.cpp// my_module/my_class.cpp #include riot/example/my_class.hpp #include iostream namespace riot { namespace example { namespace { constexpr const char default_name[] my object; } // namespace anonymous my_class::my_class() : name_(default_name) { // nop } my_class::~my_class() { // nop } void my_class::print_name() const { std::cout name() std::endl; } void my_class::do_something() { if (name() default_name) { std::cout You didnt gave me a proper name, so I refuse to do something. std::endl; } else { std::cout You gave me the name name() ... Do you really think Im willing to do something for you after insulting me like that? std::endl; } } void my_class::do_something_else() { switch (default_name[0]) { case a: // handle a break; case b: // handle b break; default: handle_default(); } } } // namespace example } // namespace riot对照示例可以立刻读到几条关键信息头文件宏保护采用相对路径_HPP形式普通注释用//、Doxygen 注释用///命名空间不增加缩进、注释中优先使用 Markdown成员变量以_结尾、同名 getter/setter 不带后缀类成员顺序为public→private实现文件中被实现的头文件位于 include 列表最前。这些规则在下文各章节逐一展开。通用规则General缩进每个缩进层级使用 4 个空格每行最多 80 个字符永远不要使用 Tab。禁止 C 风格强制类型转换一律使用 C 的static_cast/reinterpret_cast/const_cast等。垂直空行只用于分隔函数函数内部不使用空行切分逻辑块改用注释标注逻辑段落。文件后缀头文件以.hpp结尾实现文件以.cpp结尾与 C 规范的.h/.c区分开。每行只声明一个变量禁止int a, b;这种写法。指针与引用绑定类型写const std::string arg而不是const std::string arg。命名空间与访问修饰符不增加缩进层级见上面示例中的namespace riot与public:。类内成员顺序固定为public、protected、private。优先使用auto除非变量无法立即初始化或你确实想要一次类型转换此时必须加注释说明该转换的必要性。禁止手动的裸资源管理如裸new/delete应使用 RAII 与智能指针。禁止typedef一律写using T X。关键字后跟空白if (...),template ...,while (...)等。左花括号与语句同行Allman 风格被明确排除void foo() { // ... }include 顺序固定按C 标准库 → C 标准库 → 第三方库 → 你自己的 RIOT 头文件排列// some .hpp file #include sys/types.h #include vector #include 3rd/party.h #include riot/fwd.hppRIOT 自身头文件一律使用双引号系统头文件使用尖括号。在.cpp文件中被实现的头文件必须排在最前面保证自包含性检查若需要平台相关头文件可以紧接着第二位置包含riot/config.hpp。函数参数顺序先输出outputs、后输入inputs与 STL 的参数约定保持一致。单参数构造函数必须加explicit防止隐式类型转换。命名规范Naming除宏与模板参数外所有名称一律小写并用下划线分隔snake_case。模板参数名使用 CamelCase。类型与变量使用名词执行动作的函数使用命令式动词用于实现元编程的类也使用动词例如remove_const对应std::remove_const的语义。私有/受保护成员变量以_结尾同名 getter 与 setter 则使用不带后缀的名称class person { public: const std::string name() const { return name_ } void name(const std::string new_name) { name_ new_name; } private: std::string name_; };泛型模板参数约定无约束的模板参数用T泛型函数实参用x变参包parameter pack在二者后加stemplate class... Ts void print(const Ts... xs) { // ... }头文件规范Headers每个.cpp文件必须有对应的.hpp文件单元测试与main.cpp是仅有的例外。每个类拥有独立的头文件/实现文件对头文件的相对路径由其完整限定名推导模块my_module中的riot::example::my_class其头文件位于path/to/my_module/riot/example/my_class.hpp源文件位于path/to/my_module/my_class.cpp。这保证了头文件路径就是命名空间的镜像#include时一目了然。所有头文件使用#define宏保护宏名形式为RELATIVE_PATH_TO_FILE_HPP即相对路径转全大写 下划线。例如上面示例中的RIOT_EXAMPLE_MY_CLASS_HPP。能前向声明就不要#include以减小编译依赖。每个库组件必须提供fwd.hpp前向声明用户 API 中用到的所有类型。每个库组件必须提供all.hpp它承载该组件的文档主页并#include用户 API 的全部头文件。小函数使用inline经验法则10 行以内。仓库中的实际 C 头文件正是这套规则的产物以 sys/cpp11-compat/include/riot/thread.hpp 为例其宏保护为RIOT_THREAD_HPP头文件路径cpp11-compat/include/riot/thread.hpp完整对应riot::thread的命名空间与类名同目录下的 sys/cpp11-compat/include/riot/mutex.hpp、sys/cpp11-compat/include/riot/chrono.hpp 也遵循每个组件提供fwd.hpp/all.hpp的约定可通过cpp11-compat模块的 Makefile 进一步查看其组织方式。语句换行Breaking Statements构造函数初始化列表需要换行时在逗号后换行缩进 4 个空格每个初始化器独占一行能在一行放下的则不必换行my_class::my_class() : my_base_class(some_function()), greeting_(Hello there! This is my_class!), some_bool_flag_(false) { // ok } other_class::other_class() : name_(tommy), buddy_(michael) { // ok }函数参数声明与调用均在逗号后换行后续参数对齐到左括号之后intptr_t channel::compare(const abstract_channel* lhs, const abstract_channel* rhs) { // ... }运算符换行在三元运算符与二元运算符之前换行运算符留在行尾/下一行行首见下方示例中位于上一行行尾、条件延续到下一行行首对齐if (today_is_a_sunny_day() it_is_not_too_hot_to_go_swimming()) { // ... }模板元编程格式Template Metaprogramming模板最初并不是为编译期算法和类型变换设计的因此 C 以大量的语法噪音惩罚元编程。RIOT 大量使用模板为了让代码在噪音中保持可读规范额外规定了几条元编程排版规则using name ...一行放不下时一律在之后立即换行。考虑元编程函数的语义例如std::conditional本质是 if-then-else那么 if 子句与两个分支各自独占一行。每打开一层模板就增加一级缩进并将收尾的、::type或::value单独放在一行using optional_result_type typename std::conditional std::is_sameresult_type, void::value, bool, optionalresult_type ::type; // think of it as the following (not valid C): auto optional_result_type conditional { if result_type void then bool else optionalresult_type };注释中给出的伪代码直观展示了这种排版的意图让conditional的三个参数像真正的 if-then-else 语句一样分层对齐。普通类型别名不受此限制面对普通模板时按开的位置对齐即可例如using response_handle_type response_handleSubtype, message, ResponseHandleTag;预处理宏Preprocessor Macros只有在无法用 inline 函数或常量达到同样效果时才使用宏。宏命名形式为RIOT_COMPONENT_NAME与头文件宏保护的命名风格一致。例如上文示例中的RIOT_EXAMPLE_MY_CLASS_HPP以及RIOT_THREAD_HPP。注释规范CommentsDoxygen 注释以///开头普通//注释不会被 Doxygen 吞掉用于纯代码内部说明。优先使用 Markdown 而非 Doxygen 格式化指令例如使用反引号包裹代码符号见示例中的my_class、STDIN。使用cmd而非\cmd即写param、return、brief。若 brief 描述以 Returns 开头可省略冗余的return。注意 RIOT 启用了JavaDoc-style autobriefbrief 描述取到第一个句号为止其后内容视为详细描述。仓库源码同样大量采用这些注释约定例如 sys/include/irq.hpp 中irq_lock类的注释使用了brief、details、return指令与反引号代码标记sys/cpp11-compat/include/riot/thread.hpp 中this_thread、thread等类也以brief Markdown 风格撰写文档。源码中的落地佐证规范如何体现在真实代码里RIOT 仓库中真实的 C 代码分布在sys/cpp11-compat、sys/include、examples、tests等目录。以下示例可以印证规范并非纸上谈兵命名空间组织与命名规则sys/cpp11-compat/include/riot/thread.hpp 定义了namespace riot类thread、thread_id均为小写加下划线的名词sys/include/irq.hpp 中class irq_lock同样是名词类 RAII的典型写法构造时irq_disable()、析构时irq_restore(state)正是禁止裸new/delete、使用 RAII规则的直接体现。头文件宏保护sys/cpp11-compat/include/riot/thread.hpp 使用#ifndef RIOT_THREAD_HPP / #define RIOT_THREAD_HPP结尾#endif // RIOT_THREAD_HPP完全符合相对路径_HPP约定。小函数 inlinethread_id的比较运算符、thread::joinable()、thread::get_id()等短函数均声明为inline与10 行以内用 inline的经验法则吻合。禁止typedef、使用usingsys/cpp11-compat/include/riot/thread.hpp 中using id thread_id;、using native_handle_type kernel_pid_t;是标准的别名写法。每行一个变量、80 列约束thread_data结构体与thread类中的每个成员声明都独占一行长参数列表如 thread 构造函数实现 中的thread_create调用在逗号后换行对齐与Breaking Statements章节一致。需要说明的是仓库内既有代码与规范并非逐条完全一致例如个别历史文件使用m_前缀成员变量或/** */注释风格规范面向的是新提交代码与渐进式重构。作为贡献者应以 CODING_CONVENTIONS_C.md 为唯一准绳配合仓库根目录 uncrustify-riot.cfg 的 uncrustify 配置自动完成 4 空格缩进、花括号位置、行宽等机械性检查再人工对照命名、头文件组织、元编程排版与注释约定即可保证代码风格与 RIOT 主线保持一致。【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表