Modern/Module C++ Style Reference | 现代C++编码/项目风格参考
| 示例代码 - 官网 - 论坛 |
|---|
import std;
int main() {
std::println("开启你的现代C++模块化之旅...");
}- 一、
标识符命名风格- 1.0 类型名 - 大驼峰
- 1.1 对象/数据成员 - 小驼峰
- 1.2 函数 - 下划线(snake_case)
- 1.3 私有表示 -
_后缀 - 1.4 其他
- 二、模块化
- 2.0 使用
import xxx替代#include <xxx> - 2.1 模块文件结构
- 2.2 使用模块
.cppm替代头文件.h、.hpp - 2.3 模块实现与接口导出分离
- 2.4 模块及模块分区命名规范
- 2.5 多文件模块和目录
- 2.6 可导出模块分区和内部模块分区
- 2.7 模块化与向前兼容
- 2.8 其他
- 2.8.1 尽可能的少使用宏
- 2.8.2 导出模板接口时注意全局静态成员
- 2.0 使用
核心思想通过
标识符风格设计, 能快速识别 - 类型、函数、数据以及封装性
import std;
namespace mcpplibs { // 1.命名空间全小写
class StyleRef { // 2.类型名大驼峰
private:
int data_; // 3.私有数据成员 xxx_
std::string fileName_; // std::string
public: // 4. 构造函数 / Rule of Five(Big Five)单独放一个 public 区域
StyleRef() { }
StyleRef(const StyleRef &obj) { /* ... */ }
StyleRef(StyleRef &&) { /* ... */ }
StyleRef & operator=(const StyleRef &) { /* ... */ }
StyleRef & operator=(StyleRef &&) { /* ... */ }
~StyleRef() { /* ... */ }
public: // 5.公有函数区域
// 函数名 下划线分割 / snake_case
/* 7. fileName 小驼峰 */
void load_config_file(std::string fileName) {
// 成员函数如无特殊要求接口和实现不分离
parse_(fileName);
}
private:
// 6.私有成员函数以 `_` 结尾
void parse_(std::string config) {
}
};
}单词首字母都大写, 单词之间不加下划线.
- 例:
StyleRef, HttpServer, JsonParser
struct StyleRef {
using FileNameType = std::string;
}; 一个单词首字母小写, 后续单词首字母大写, 不加下划线
- 例:
fileName, configText
struct StyleRef {
std::string fileName;
};
StyleRef mcppStyle;全小写(通常), 单词用下划线连接
- 例:
load_config_file(), parse_(), max_retry_count()
class StyleRef {
public:
void load_config_file(std::string fileName) {
}
};在标识符后加上
_表示是对外部不可访问/私有的数据, 即可以是数据成员也可以是函数
- 例:
fileName_, parse_
class StyleRef {
private:
std::string fileName_;
void parse_(std::string config) {
}
};- 全局数据/成员, 通过前缀
g表示. 例如:StyleRef gStyleRef;
旧式#include写法
#include <print>;
int main() {
std::println("Hello, MC++!");
}模块导入写法
import std;
int main() {
std::println("Hello, MC++!");
}// 0.全局模块片段(可选)
module; // 当需要使用传统头文件时使用
// 1.传统头文件引入区域
#include <xxx>
// 2.模块声明和导出
export module module_name;
// 2.1 导入导出模块分区接口 (可选)
//export module :xxxx;
// 3.模块导入区域
import std;
import xxx;
// 3.1 导入模块分区接口 (可选)
//import :xxxxx;
// 4.接口导出与实现区域
export int add(int a, int b) {
return a + b;
}可以把导出接口和接口实现都放到
.cppm文件中.
传统风格1 - .cpp + .h
// mcpplibs.h
#ifndef MCPPLIBS_H
#define MCPPLIBS_H
int add(int a, int b);
#endif// mcpplibs.cpp
#include <mcpplibs.h>
int add(int a, int b) {
return a + b;
}传统风格2 - .hpp
// mcpplibs.hpp
#ifndef MCPPLIBS_HPP
#define MCPPLIBS_HPP
int add(int a, int b) {
return a + b;
}
#endif模块中的接口, 默认外界是不能使用的.要导出的接口需要在前面加
export关键字
// mcpplibs.mcpp
// 模块导出
export module mcpplibs;
// 接口导出
export int add(int a, int b);
// 接口实现
int add(int a, int b) {
return a + b;
}或
// mcpplibs.mcpp
// 模块导出
export module mcpplibs;
// 接口导出 & 实现
export int add(int a, int b) {
return a + b;
}通过命名空间隔离模块实现和接口导出, 可以有选择的控制导出接口
// mcpplibs.mcpp
// 模块导出
export module mcpplibs;
// 模块的具体实现 / 私有接口
namespace mcpplibs_impl {
int add(int a, int b) {
return a + b;
}
int other(int a, int b) {
return a + b;
}
};
// 导出整个命名空间
// 然后把部分需要导出的接口放到这个导出空间中
export namespace mcpplibs {
using mcpplibs_impl::add;
};使用文件(及目录)名 +
.层级分割符进行模块命名, 降低同名模块冲突概率
- 模块名格式:
topdir.subdir1.subdir2.filename - 模块分区名:
topdir.subdir1.subdir2.module_filename:filename
.
├── a
│ └── c.cppm
├── b
│ └── c.cppm
└── main.cppa/c.cppm
//...
export module a.c;
//...b/c.cppm
//...
export module b.c;
//...import std;
import a.c;
import b.c;
int main() {
//...
}当一个模块内容太多, 需要进一步分文件时可以采用
目录+模块或模块分区组合的方式进行实现
- 一个文件夹 + 一个模块声明文件
- 文件夹: 同一模块的实现分散到不同文件实现
- 模块文件: 对外接口导出的汇总文件
.
├── a // 模块a实现
│ ├── a1.cppm // a:a1
│ ├── a2.cppm // a:a2
│ ├── b // 模块a.b实现
│ │ ├── b1.cppm // a.b:b1;
│ │ └── b2.cppm // a.b:b2;
│ ├── b.cppm // 模块a.b声明及导出
│ └── c.cppm // 独立模块a.c的实现 + 声明及导出(都在一个文件)
├── a.cppm // 模块a声明及导出
└── main.cpp
3 directories, 7 filesa.b模块
模块的导出文件
// a/b
export module a.b;
export import :b1; // 导出a.b的b1分区
export import :b2; // 导出a.b的b2分区
//...a.b:b1模块分区
// a/b/b1.cppm
export module a.b:b1;
//...a.b:b2模块分区
// a/b/b2.cppm
export module a.b:b2;
//...a模块
// a.cppm
export module a;
export import a.b;
export import :a2; // 导入&出模块a的分区a2
import std;
import :a1; // 导入模块a的内部分区a1
export namespace a {
//...
}当一个模块有多个分区时,应区分可导出的分区以及仅供内部使用的分区
a.a2可导出模块分区
通过
export修饰模块分区
// a/a2.cppm
export module a:a2;
//...a.a1内部模块分区
内部模块分区, 不能使用
export来修饰模块名和分区里的接口
// a/a1.cppm
module a:a1;
//...
// export void test() { } // error
void test() { } // okexport module a;
export import :a2; // 使用可导出分区要加export
import :a1; // 使用内部分区不能加export且只能模块内部使用C/C++已有生态中没有模块化的库, 可以把项目所有引入的传统头文件库封装到一个专门的"兼容模块中", 然后在该模块中把接口进行"重新导出", 来最小化
全局模块/传统头文件的使用范围
c语言lua库, 以lua.cppm的方式用模块化"重导入"
module;
#ifdef __cplusplus
extern "C" {
#endif
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#ifdef __cplusplus
}
#endif
export module lua;
//import std;
export namespace lua {
using lua_State = ::lua_State;
using lua_CFunction = ::lua_CFunction;
//...
}...