学习现代的C++ | 资源管理 | SFINAE

  1. 资源管理

    C++标准里自由存储区,freestore,new 和 delete 操作的区域是 free store
    malloc 和 free 操作的区域是 heap
    两者区别:new delete不一定使用malloc free,那么自由存储区!= 堆

    RAII:Resource Acquisition Is Initialization, 依托栈和析构函数,进行资源管理,编译器会自动调用析构函数,包括在函数执行发生异常的情况(栈展开),(关闭文件,释放锁,释放资源)
    java 垃圾回收机制:可达性分析后,标记不可达内存区域,根据算法清除方法区和堆中垃圾

    释放内存,delete前可能会有异常,内存泄漏
    栈:

    栈的增长方向是低地址,当函数调用另外一个函数时,会把参数也压入栈里(我们此处忽略使用寄存器传递参数的情况),然后把下一行汇编指令的地址压入栈,并跳转到新的函数。新的函数进入后,首先做一些必须的保存工作,然后会调整栈指针,分配出本地变量所需的空间,随后执行函数中的代码,并在执行完毕之后,根据调用者压入栈的地址,返回到调用者未执行的代码中继续执行。

    堆:

    不能存储在栈上的情况:
    对象很大;对象的大小在编译时不能确定;对象是函数的返回值,但由于特殊的原因,不应使用对象的值返回

    静态存储区:编译时确定,和堆栈不同
    非静态数据成员加上动态类型所需的空间。注意后者不一定是4,而一般是指针的大小,在64位系统上是8字节。还有,要考虑字节对齐的影响。静态数据成员和成员函数都不占个别对象的空间

    工厂方法返回基类指针,如果返回对象,会产生对象切片

  2. 智能指针

    unique_ptr 一个对象只能被单个指针拥有;shared_ptr 一个对象被多个指针拥有,共享引用计数。

    // 成员函数
    T& operator*() const noexcept { return *ptr_; }  
    T* operator->() const noexcept { return ptr_; }  
    operator bool() const noexcept { return ptr_; }
    

    if (this != &rhs),赋值过程中发生异常,this内容可能被破坏掉。这样写,先复制再交换,复制过程中异常也不影响this:

    smart_ptr& operator=(smart_ptr rhs)
    {   
        rhs.swap(*this);
        return *this;
    }
    

    模板的移动构造函数不被编译期视为真的移动构造,仍然会帮助生成拷贝构造函数。

  3. 右值与移动

    类型是右值引用的变量是一个左值,有标识符,可以取地址
    std::move(ptr1) 有名字的右值,xvalue

    如果一个 prvalue 被绑定到一个引用上,它的生命周期则会延长到跟这个引用变量一样长。对 xvalue
    无效。如果由于某种原因,prvalue 在绑定到引用以前已经变成了 xvalue,那生命期就不会延长。

    右值的目的:实现移动,减少运行开销。智能指针中,使用移动后,减少一次addCount计数,析构时减少reduceCount。
    使对象支持移动的技巧:

    ① 移动构造函数
    ② swap成员函数
    ③ 全局的swap函数,调用成员函数,更便捷
    ④ 实现通用的 operator=
    ⑤ 声明为noexcept

    返回值优化(NRVO),把对象直接构造到调用者的栈上。从 C++11 开始,返回值优化仍可以发生,但在没有返回值优化的情况下,编译器将试图把本地对象移动出去,而不是拷贝出去。对返回值临时变量,std::move,会阻止NOVO,多此一举。
    引用坍缩:

    对于 template foo(T&&) 这样的代码,如果传递过去的参数是左值,T 的推导结果是左值引用;如果传递过去的参数是右值,T 的推导结果是参数的类型本身。
    如果 T 是左值引用,那 T&& 的结果仍然是左值引用——即 type& && 坍缩成了 type&。
    如果 T 是一个实际类型,那 T&& 的结果自然就是一个右值引用

    完美转发:保持参数的左值右值类别进行转发。std::forward

  4. 容器
    string_view 是只读的轻量对象,传参时不会拷贝。
    vector 连续内存,访问快,大小增长导致移动,可以提前reserve空间。
    deque 双端队列,头尾添加删除元素快速,空间部分连续,下标访问效率也比较高。
    list O(1) 复杂度的任意位置的插入和删除操作,每个元素内存空间单独分布,遍历性能低,插入高效。
    forward_list 单向列表,内存占用少7
    适配器:queue FIFO ,缺省deque
    stack LIFO,缺省deque
    priority_queue 排序

    函数对象及其特化 less 、hash
    有序关联容器:set(集合)、map(映射)、multiset(多重集)和 multimap(多重映射),存储在关联容器中的键一般应满足严格弱序关系
    lower_bound(k) 找到第一个不小于查找键 k 的元素(!(x < k))
    upper_bound(k) 找到第一个大于查找键 k 的元素(k < x)

    无序关联容器:unordered_set、unordered_map、unordered_multiset 、unordered_multimap,实现使用哈希表
    数组: C++17 array 栈上分配, std::size(arr)

  5. 异常
    异常安全是指当异常发生时,既不会发生资源泄漏,系统也不会处于一个不一致的状态。如果一个函数声明了不会抛出异常、结果却抛出了异常,C++ 运行时会调用 std::terminate 来终止应用程序。
    vector 会在元素类型没有提供保证不抛异常的移动构造函数的情况下,在移动元素时会使用拷贝构造函数。

  6. 迭代器
    迭代器 对象可以被拷贝构造、拷贝赋值和析构;对象支持 * 运算符;对象支持前置 ++ 运算符
    输入迭代器 input iterator, 只要求可以单次访问
    前向迭代器 forward iterator ,允许多次访问
    双向迭代器 bidirectional iterator, forward 支持 –
    随机访问迭代器 random-access iterator ,双向迭代器,如果额外支持在整数类型上的 +、-、+=、-=,跳跃式地移动迭代器
    (C++20)连续迭代器 contiguous iterator,迭代器指向的对象在内存中是连续存放的

    输出迭代器 back_inserter_iterator

    vector<int> v1{1, 2, 3, 4, 5};
    vector<int> v2;
    copy(v1.begin(), v1.end(), back_inserter(v2));
    
  7. 易用性改进
    auto推断

    decltype:
    decltype(a) 类型
    decltype((a)) 引用
    decltype(a + a) 类型
    C++14 decltype(auto) a = expr; 既可以是值类型,也可以是引用类型;用在通用的转发函数模板中
    列表初始化:vector v{1, 2, 3, 4, 5}; initializer_list
    统一初始化:几乎可以在所有初始化对象的地方使用大括号而不是小括号,不可窄化,{1.0} 不可调用 obj(int)
    二进制字面量:unsigned mask = 0b111000000;

    静态断言:static_assert(编译期条件表达式, 可选输出信息);

    default delete成员函数:

    没有初始化的非静态 const 数据成员和引用类型数据成员会导致默认提供的默认构造函数被删除;
    非静态的 const 数据成员和引用类型数据成员会导致默认提供的拷贝构造函数、拷贝赋值函数、移动
    构造函数和移动赋值函数被删除;
    用户如果没有自己提供一个拷贝构造函数(必须形如 Obj(Obj&) 或 Obj(const Obj&);不是模板),编译器会隐式声明一个;
    用户如果没有自己提供一个拷贝赋值函数(必须形如 Obj& operator=(Obj&) 或 Obj& operator=(const Obj&);不是模板),编译器会隐式声明一个;
    用户如果自己声明了一个移动构造函数或移动赋值函数,则默认提供的拷贝构造函数和拷贝赋值函数被删除;
    用户如果没有自己声明拷贝构造函数、拷贝赋值函数、移动赋值函数和析构函数,编译器会隐式声明一个移动构造函数;
    用户如果没有自己声明拷贝构造函数、拷贝赋值函数、移动构造函数和析构函数,编译器会隐式声明一个移动赋值函数。

    override:显式声明了成员函数是一个虚函数且覆盖了基类中的该函数
    final:声明了成员函数是一个虚函数,且该虚函数不可在派生类中被覆盖

  8. 要不要返回对象

    注意对于本地变量,永远不要返回引用或者指针,会导致未定义行为,如果有析构函数释放本地变量内存,访问到的内存是被破坏的。
    如果一个对象可拷贝可赋值,也可以默认构造,成为半正则的。
    返回值优化,拷贝消除。
    c++17对不可拷贝、不可移动的对象,仍然是可以被返回的。
    F.20 在函数输出数值时,尽量使用返回值而非输出参数。

  9. Unicode

    最早的中文字符集标准:GB2312
    Unicode 文本文件通常有一个使用 BOM(byte order mark)字符的约定,区分各种编码。
    Unix宽字符串wcout char32_t u32string,区域设置

    UTF-8:1 到 4 字节的变长编码。在一个合法的 UTF-8 的序列中,如果看到一个字节的最高位是 0,那就是一个单字节的
    Unicode 字符;如果一个字节的最高两比特是 10,那这是一个 Unicode 字符在编码后的后续字节;否则,这就是一个 Unicode
    字符在编码后的首字节。

  10. 编译期多态
    容器类多共性,鸭子类型,不必继承。
    类模板和函数模板,编译器在定义时只做基本的语法检查,实例化时进行类型检查。编译过程中可能产生多个这样的实例,链接时最后剩下一个实例。
    template class vector; 显示实例化
    extern template class vector; 外部实例化, 告诉编译器不需要实例化
    对函数模板进行重载,对类模板进行特化。

  11. 编译期计算(模板元编程)

    编译期编程,需要把计算转变成类型推导。e.m

    template <bool cond,
              typename Then,
              typename Else>
    struct If;
    
    template <typename Then,
              typename Else>
    struct If<true, Then, Else> {
      typedef Then type;
    };
    
    template <typename Then,
              typename Else>
    struct If<false, Then, Else> {
      typedef Else type;
    };
    

    用 :: 取一个成员类型、并且 :: 左边有模板参数的话,得额外加上 typename 关键字来标明结果是一个类型。
    <type_traits> ,提取某个类型在某方面的特点,特点既是类型,又是常值。
    值和类型转换:integral_constant

  12. SFINAE 替换失败非错

    函数模板实例化过程:
    1.调用函数与函数模板名称匹配时,根据名称找出所有适用的函数和函数模板;
    2.适用的模板形参替换,替换过程发生错误则丢弃;
    3.找到一个最佳匹配产生该函数的调用;
    4.如果没有找到最佳匹配,或者有多个,编译器报错。

    SFINAE:实例化过程失败时,会继续寻找其他函数模板重载,这个特性可用于检测一个类是否有某个成员函数。

    enable_if 选择性启用某个函数的重载。比如,根据SFINAE特性,检测container容器是否有reserve函数,如果有就事先调用reserve函数预留空间,避免不必要的移动和拷贝。
    enable_if_t<has_reserve::value, void> : 如果类型C有reserve函数,就启用下面的成员函数,并且返回类型为void。

    使用decltype返回值
    declval :声明某类型的参数,仅用于匹配模板,不实际使用,某类型没有默认构造函数时,假想出一个该类的对象进行类型推导。
    declval<C&>().reserve(xxx) 测试C&类型的对象是否可用xxx调用reserve

    true_type false_type 标签分发

  13. constexpr

    C++17 产生内敛变量概念 , static const int = 1; 缺省不内敛 ,需要额外定义
    不使用动态内存分配的类型, 析构函数是平凡的(啥也不做)的类型,可以称为字面类型,可以使用constexpr编译期计算

  14. lambda表达式

    每一个lambda表达式都是一个全局唯一的类型,需要使用auto; 也可function模板,但是消耗更多性能
    按引用捕获的变量需要保证 变量和表达式的生命周期一样长
    泛型lambda表达式,参数类型auto:auto func = [](auto x, auto y){}

    std::bind 的参数数量:函数对象的参数数量+1,比如bind(plus<>(), _1, _3) , 或者bind(plus<>(), _1, 100) 生成新的函数对象
    std::function:

    std::function<const int&()> F([]{ return 42; });
    int x = F(); // 未定义行为: F() 的结果是悬垂引用

  15. 函数式编程

    命令式编程:
    高阶函数 accumulate 归并
    partition 满足条件放在前面
    remove_if 不满足条件的放在前面

    说明式编程:sql

    自动的并行计算:std::reduce(std::execution::par, v.begin(), v.end());

  16. 工具
    Valgrind
    nvwa::debug_new http://wyw.dcweb.cn/leakage.htm
    Compiler Explorer
    https://cppinsights.io/
    Clang-Tidy
    Cppcheck
    Clang-Format

  17. 可变模板
    转发参数 std::forward,确保参数转发时仍然保持正确的左值或右值引用类型
    递归用法

    tuple_size_v (在编译期)取得多元组里面的项数
    std::apply , 编译时解析tuple pair array

  18. 异步
    进程:独立地址空间,管理堆内存,套接字、文件,进程退出时返回给操作系统
    thread 要求在析构之前要么 join(阻塞直到线程退出),要么 detach(放弃对线程的管理),否则程序会异常退出
    mutex 多次加锁,未定义行为
    lock_guard
    未来量:future 可以代替条件变量通知,future.get() 一个future只能调用一次
    承诺量:promise 和future一起使用,一次性管道
    packed_task
    volatile 关键字可以达到内存同步作用,但是不能通用地达到内存同步的效果
    c++11 内存模型,store(2, memory_order_release); load(memory_order_acquire);acquire 和 release 同步出现
    原子操作; 内存序

    读:在读取的过程中,读取位置的内容不会发生任何变动。
    写:在写入的过程中,其他执行线程不会看到部分写入的结果。
    读‐修改‐写:读取内存、修改数值、然后写回内存,整个操作的过程中间不会有其他写入操作插入,其他执行线程不会看到部分写入的结果。

  19. C++20
    concepts 概念
    Ranges 可以使用管道;基于概念
    Coroutines 协程(备注:协程与子程序不同,可中断先去执行其他,在一个线程中执行)

  20. Boost
    sudo apt-get install libboost-dev
    Boost.TypeIndex
    Boost.Core
    Boost.Conversion
    Boost.Hana 编译期

  21. 单元测试
    Boost.Test
    Catch2

  22. 日志
    Easylogging++ 性能跟踪
    spdlog

  23. 网络
    RestSDK HTTP

参考: 极客时间 吴咏炜老师 现代 C++ 课程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值