• 【摘要】Cpp核心指南


    CppCoreGuidelines是一篇比较好的cpp学习指南。本想认真翻译下,但在github上发现已经有了中文翻译。先摘取比较重要的章节学习下。

    P: 理念

    P.1: 在代码中直接表达你的想法

    P.2: 用 ISO 标准 C++ 来编码

    P.3: 表达你的设计意图

    程序员应当熟悉:

    P.4: 理想情况下,程序应当是静态类型安全的

    但有些场景无法在编译器确定静态安全类型:

    • union - 使用 variant(C++17 提供)
    • 强制转换 - 尽可能减少其使用;使用模板有助于这点
    • 数组退化 - 使用 span(来自 GSL)
    • 范围错误 - 使用 span
    • 窄化转换 - 尽可能减少其使用,必须使用时则使用 narrow 或者 narrow_cast(来自 GSL)

    P.5: 编译期检查优先于运行时检查

    P.6: 应当使无法在编译期进行的检查能够在运行时实施

    示例中,尽量保障交互是变量生命周期的有效性及安全。

    extern void f4(vector&);   // 分离编译,可能会被动态加载
    extern void f4(span);      // 分离编译,可能会被动态加载
                                    // NB: 这里假定调用代码是 ABI 兼容的,使用的是
                                    // 兼容的 C++ 编译器和同一个 stdlib 实现
    
    void g3(int n)
    {
        vector v(n);
        f4(v);                     // 传递引用,保留所有权
        f4(span{v});          // 传递视图,保留所有权
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    P.8: 不要泄漏任何资源

    基于RAII的方式而不用基于fopen的方式避免内存泄漏。具体可参考资源管理

    void f(char* name)
    {
        ifstream input {name};
        // ...
        if (something) return;   // OK: 没有泄漏
        // ...
    }
    
    // BAD
    void f(char* name)
    {
        FILE* input = fopen(name, "r");
        // ...
        if (something) return;   // 不好的:如果 something == true 的话,将会泄漏一个文件句柄
        // ...
        fclose(input);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    P.9: 不要浪费时间或空间

    // BAD:每次都计算strlen
    void lower(zstring s)
    {
        for (int i = 0; i < strlen(s); ++i) s[i] = tolower(s[i]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    P.10: 不可变数据优先于可变数据

    具体参考:Con: 常量和不可变性

    P.11: 把杂乱的构造封装起来,而别让其散布到代码中

    P.12: 适当采用支持工具

    P.13: 适当采用支持程序库

    I: 接口

    I.1: 使接口明确

    I.2: 避免非 const 全局变量

    I.3: 避免使用单例

    I.4: 使接口严格和强类型化

    I.5: 说明前条件(如果有)

    I.6: 优先使用 Expects() 来表达前条件

    I.7: 说明后条件

    I.8: 优先使用 Ensures() 来表达后条件

    I.9: 当接口是模板时,用概念来文档化其参数

    template
      requires input_iterator && equality_comparable_with, Val>
    Iter find(Iter first, Iter last, Val v)
    {
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    参见: 泛型编程和概念

    I.10: 使用异常来表明无法实施所要求的任务

    I.11: 决不以原始指针(T*)或引用(T&)来传递所有权

    I.12: 把不能为空的指针声明为 not_null

    I.13: 不要只用一个指针来传递数组

    void copy_n(const T* p, T* q, int n); // 从 [p:p+n) 复制到 [q:q+n)
    ->
    void copy(span r, span r2); // 将 r 复制给 r2
    
    
    • 1
    • 2
    • 3
    • 4

    I.22: 避免全局对象之间进行复杂的初始化

    复杂的初始化可能导致未定义的执行顺序。

    I.23: 保持较少的函数参数数量

    template
    OutputIterator merge(InputIterator1 first1, InputIterator1 last1,
                         InputIterator2 first2, InputIterator2 last2,
                         OutputIterator result, Compare comp);
    ->
    template
    OutputIterator merge(InputRange1 r1, InputRange2 r2, OutputIterator result);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    I.24: 避免可以由同一组实参以不同顺序调用造成不同含义的相邻形参

    I.25: 优先以空抽象类作为类层次的接口

    空的(没有非静态成员数据)抽象类要比带有状态的基类更倾向于保持稳定。

    class Shape {  // 不好: 接口类中加载了数据
    public:
        Point center() const { return c; }
        virtual void draw() const;
        virtual void rotate(int);
        // ...
    private:
        Point c;
        vector outline;
        Color col;
    };
    
    class Shape {    // 有改进: Shape 是一个纯接口
    public:
        virtual Point center() const = 0;   // 纯虚函数
        virtual void draw() const = 0;
        virtual void rotate(int) = 0;
        // ...
        // ... 没有数据成员 ...
        // ...
        virtual ~Shape() = default;        
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    I.26: 当想要跨编译器的 ABI 时,使用一个 C 风格的语言子集

    不同的编译器会实现不同的类的二进制布局,异常处理,函数名字,以及其他的实现细节。

    I.27: 对于稳定的程序库 ABI,考虑使用 Pimpl 手法

    由于私有数据成员参与类的内存布局,而私有成员函数参与重载决议, 对这些实现细节的改动都要求使用了这类的所有用户全部重新编译。而持有指向实现的指针(Pimpl)的 非多态的接口类,则可以将类的用户从其实现的改变隔离开来,其代价是一层间接。

    I.30: 将有违规则的部分封装

    F: 函数

    F.1: 把有意义的操作“打包”成为精心命名的函数

    F.2: 一个函数应当实施单一一项逻辑操作

    F.3: 保持函数短小简洁

    F.4: 如果函数可能必须在编译期进行求值,就将其声明为 constexpr

    需要用 constexpr 来告诉编译器允许对其进行编译期求值。

    constexpr int min(int x, int y) { return x < y ? x : y; }
    
    void test(int v)
    {
        int m1 = min(-1, 2);            // 可能进行编译期求值
        constexpr int m2 = min(-1, 2);  // 编译期求值
        int m3 = min(-1, v);            // 运行期求值
        constexpr int m4 = min(-1, v);  // 错误: 无法在编译期求值
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    F.5: 如果函数非常小,并且是时间敏感的,就将其声明为 inline

    F.6: 如果函数必然不会抛出异常,就将其声明为 noexcept

    如果不打算抛出异常的话,程序就会认为无法处理这种错误,并且应当尽早终止。把函数声明为 noexcept 对优化器有好处,因为其减少了可能的执行路径的数量。它也能使发生故障之后的退出动作有所加速。

    F.7: 对于常规用法,应当接受 T* 或 T& 参数而不是智能指针

    F.8: 优先采用纯函数

    TOC

  • 相关阅读:
    Linux篇17多线程第一部分
    基于Nginx和Consul构建自动发现的Docker服务架构——非常之详细
    Zig标准库:最全数据结构深度解析(1)
    4061 Magic Multiplication,思维,枚举,优化
    Spring注解驱动之ServletContainerInitializer使用
    SystemUI状态栏
    界面控件开发包DevExpress v23.1.6全新发布|附高速下载
    【鸿蒙软件开发】ArkTS基础组件之TextTimer(文本显示计时)、TimePicker(时间选择)
    点到直线距离
    2022年全球市场硝普钠原料药总体规模、主要生产商、主要地区、产品和应用细分研究报告
  • 原文地址:https://blog.csdn.net/CRISPY_RICE/article/details/126035870