跳过正文
P19 高级话题与新标准
  1. S/
  2. C/C++/
  3. C/
  4. C语言程序设计/

P19 高级话题与新标准

Li
作者
Li
往前走,别回头!
目录
C++ 新经典笔记 - 这篇文章属于一个选集。

知识点
#

20.1 函数调用运算符与 function 类模板
#

  • @ 函数对象图 ![[白板/P61 函数对象与可调用对象.canvas|函数对象与可调用对象]]

20.1.1 函数调用运算符
#

[!tip] 函数调用运算符

  • ( ) 是函数调用运算符
  • 在类中重载 operator(),可以像函数一样使用该类的对象
  • ==重载了 ( ) 的类对象就是可调用对象(函数对象)==
class biggerthanzero {
public:
    int operator()(int value) const {
        if (value < 0) return 0;
        return value;
    }
};

biggerthanzero obj;
int result = obj(-5);  // 调用 operator(),返回 0

20.1.2 不同调用对象的相同调用形式
#

[!tip] 调用形式相同

  • 函数和重载 operator() 的类对象,如果形参和返回值相同,则"调用形式相同"
  • 一种调用形式对应一个函数类型
  • int(int) 代表一个函数类型:接收一个 int 参数,返回一个 int

20.1.3 标准库 function 类型简介
#

[!tip] std::function 类模板

  • C++11 标准库中的类模板,用于包装可调用对象
  • function<int(int)> 可以包装任何接收 int 返回 int 的可调用对象
  • ==统一了不同可调用对象的类型==
#include <functional>
function<int(int)> f1 = echovalue;     // 函数指针
function<int(int)> f2 = obj;            // 类对象
function<int(int)> f3 = biggerthanzero(); // 临时对象
  • ! 函数是重载的时,无法直接包装进 function,需用函数指针解决二义性

20.2 万能引用
#

  • @ 万能引用图 ![[白板/P62 万能引用与类型推断.canvas|万能引用与类型推断]]

20.2.1 类型区别基本概念
#

[!tip] 类型模板参数 T

  • T 是类型模板参数,有类型
  • tmprv 是函数形参,也有类型
  • ==T 的类型不仅取决于实参,还取决于形参的类型==

20.2.2 universal reference 基本认识
#

[!tip] 万能引用(universal reference)

  • 又名转发引用(forwarding reference)、未定义引用
  • ==万能引用是一种类型,长得像右值引用 T&&==
  • 必须同时满足两个条件:
    1. 必须是函数模板
    2. 必须发生模板类型推断,形参为 T&&

[!tip] 万能引用 vs 右值引用

特性右值引用万能引用
形式int&&T&&(模板中)
绑定左值不可以可以
绑定右值可以可以
类型推断
template <typename T>
void myfunc(T&& tmprv) {  // 万能引用
    tmprv = 12;
}

int i = 100;
myfunc(i);        // 左值,tmprv 为 int&
myfunc(std::move(i)); // 右值,tmprv 为 int&&
  • ! 判断题:
    • void func(int&& param) → 右值引用(不是函数模板)
    • template<typename T> void func(T&& tmpvalue) → 万能引用
    • template<typename T> void func(std::vector<T>&& param) → 右值引用(T和&&不挨着)

20.2.3 万能引用资格的剥夺与辨认
#

[!tip] 万能引用资格剥夺

  • const 修饰词会剥夺万能引用资格,退化为右值引用
  • template<typename T> void myfunc(const T&& tmprv) → 右值引用

[!tip] 万能引用辨认

  • 类模板的成员函数 void testfunc(T&& x) → 右值引用(无类型推断)
  • 成员函数模板 template<typename T2> void testfunc2(T2&& x) → 万能引用(有类型推断)

20.3 理解函数模板类型推断与查看类型推断结果
#

  • @ 类型推断图 ![[白板/P62 万能引用与类型推断.canvas|万能引用与类型推断]]

20.3.1 如何查看类型推断结果
#

[!tip] Boost库 typeindex

#include <boost/type_index.hpp>
using boost::typeindex::type_id_with_cvr;
cout << "T = " << type_id_with_cvr<T>().pretty_name() << endl;
cout << "tmprv = " << type_id_with_cvr<decltype(tmprv)>().pretty_name() << endl;

20.3.2 理解函数模板类型推断
#

[!tip] 指针或引用类型(非万能引用)

  • T& tmprv
    • 实参是引用 → 引用部分被忽略,T 不推导为引用
    • 实参是 constconst 属性会成为 T 的组成部分
  • const T& tmprv:T 中的 const 消失(形参已有 const
  • T* tmprv:实参的 const 会带到 T 类型中

[!tip] 万能引用类型推断

实参类型T 类型tmprv 类型
左值 int iint&int&
左值 const int jint const &int const &
右值 100intint&&

[!tip] 传值方式

  • 引用类型实参 → 引用部分被忽略
  • const 类型实参 → const 部分被忽略(产生新副本)
  • 指针的常量性被忽略,指向内容的常量性被保留

[!tip] 数组作为实参

  • T tmprv → 数组退化为指针
  • T& tmprv → T 推导为数组类型(含尺寸)
  • template<typename T, unsigned L> void myfunc(T(&tmprv)[L]) → 可获取数组长度

[!tip] 函数名作为实参

  • T tmprv → 函数指针
  • T& tmprv → 函数引用

20.4 引用折叠、转发、完美转发与 forward
#

  • @ 引用折叠图 ![[白板/P63 引用折叠与完美转发.canvas|引用折叠与完美转发]]

20.4.1 引用折叠规则
#

[!tip] 引用折叠规则

  • 编译器内部遇到超过两个 & 时进行合并
  • ==如果任意一个引用为左值引用,结果就为左值引用(左值引用会传染)==
组合折叠结果
& + &&(左值引用)
& + &&&(左值引用)
&& + &&(左值引用)
&& + &&&&(右值引用)
  • ! 程序中不能直接写引用的引用(如 int& &byy),编译报错

20.4.2 转发与完美转发
#

[!tip] 转发问题

  • 函数模板接收参数后转发给其他函数
  • 普通引用形参无法保存实参的左值/右值信息
  • ==万能引用可以保存实参的所有类型信息==

[!tip] 完美转发问题

  • 右值实参传递到函数模板后变成左值(形参本身是左值)
  • 再转发给需要右值引用的函数时会失败
  • 需要保持原始实参的左值/右值性

20.4.3 std::forward
#

[!tip] std::forward 完美转发

  • C++11 标准库函数,专门为转发而存在
  • ==保持原始实参的左值或右值性==
  • 只对原来是右值的情况有用(强制把左值转成右值)
template<typename F, typename T1, typename T2>
void myFuncTemp(F f, T1&& t1, T2&& t2) {
    f(std::forward<T1>(t1), std::forward<T2>(t2));
}

[!tip] std::forward 工作原理

  • std::forward<T>(t) 从 T 中取出类型信息
  • T 是 int → 转成右值
  • T 是 int& → 保持左值

20.4.4 std::move 和 std::forward 的区别
#

[!tip] move vs forward

特性std::movestd::forward
转换方式无条件转右值有条件转换
参数一个普通参数模板类型参数 + 普通参数
用途为移动操作做准备参数转发
后接内容左值万能引用

20.5 理解 auto 类型推断与 auto 应用场合
#

  • @ auto类型推断图 ![[白板/P62 万能引用与类型推断.canvas|万能引用与类型推断]]

20.5.1 auto 类型常规推断
#

[!tip] auto 类型推断规则

  • auto 相当于函数模板中的类型模板参数 T
  • 发生在编译期,定义变量时必须立即初始化

[!tip] 传值方式(非指针、非引用)

  • ==会抛弃引用、const 等限定符==
  • auto x = 27;xint
  • const auto x2 = x;x2const int
  • auto xy2 = xy;xyconst int&) → xy2int

[!tip] 指针或引用类型(非万能引用)

  • ==不会抛弃 const,但会抛弃引用==
  • auto& xy3 = xy;xyconst int&) → xy3const int&autoconst int
  • auto* xp2 = &x;xp2int*

[!tip] 万能引用类型

  • auto&& wnyy0 = 222; → 右值,wnyy0int&&
  • auto&& wnyy1 = x; → 左值,发生引用折叠,wnyy1int&

20.5.2 auto 类型针对数组和函数的推断
#

[!tip] 数组推断

  • auto myarr = mystr;myarrchar const*(指针)
  • auto& myarr2 = mystr;myarr2char const(&)[14](数组引用)

[!tip] 函数推断

  • auto tmpf = myfunc3; → 函数指针
  • auto& tmpf2 = myfunc3; → 函数引用

20.5.3 auto 类型 std::initializer_list 的特殊推断
#

[!tip] initializer_list 特殊推断

  • auto x3 = {30};x3std::initializer_list<int>
  • auto x4{30};x4int(无等号)
  • ==只有 auto 有这种特殊推断,函数模板没有==

20.5.4 auto 不适用场合
#

  • ! 普通成员变量不能是 auto 类型
  • ! static const auto 成员可以使用(必须在类内初始化)

20.5.5 auto 适用场合
#

[!tip] auto 适用场合

  • 迭代器类型简化:for (auto iter = mymap.begin(); ...)
  • 无法确定类型时:auto value = T::testr();

20.6 详解 decltype 含义与 decltype 主要用途
#

  • @ decltype图 ![[白板/P64 decltype与类型萃取.canvas|decltype与类型萃取]]

20.6.1 decltype 含义和举例
#

[!tip] decltype 特点

  • 返回操作数的数据类型
  • 发生在编译期,不真正计算表达式的值
  • ==不会抛弃 const、引用等属性==

[!tip] decltype 后是变量

  • decltype(i) j2 = 15;iconst int) → j2const int
  • decltype(iy) j3 = j2;iyconst int&) → j3const int&

[!tip] decltype 后是表达式

  • decltype(i + 1) j;jint
  • decltype(*pi) k3 = i;k3int&(能做左值的表达式返回引用)
  • decltype((i)) iy3 = i;iy3int&(双层括号变成表达式)

[!tip] decltype 后是函数

  • decltype(testf()) tmpv = 1;tmpvint(函数返回类型)
  • decltype(testf) tmpv2;tmpv2 为函数类型

20.6.2 decltype 主要用途
#

[!tip] 应付可变类型

template<typename T>
class CTTMP {
public:
    decltype(T().begin()) iter;  // 自动推断迭代器类型
};

[!tip] 返回类型后置语法

auto add(int i, int k) -> decltype(i + k) {
    return i + k;
}

[!tip] decltype(auto) 用法(C++14)

  • 用于函数返回类型:保持引用属性
  • 用于变量声明:捡回 auto 丢掉的 const、引用
template<typename T>
decltype(auto) mydouble(T& vl) {
    vl *= 2;
    return vl;  // 返回引用
}

20.7 可调用对象、std::function 与 std::bind
#

  • @ 可调用对象图 ![[白板/P61 函数对象与可调用对象.canvas|函数对象与可调用对象]]

20.7.1 可调用对象
#

[!tip] 可调用对象分类

  1. 函数指针void(*paf)(int) = myfunc;
  2. 仿函数:重载 operator() 的类对象
  3. 可转换为函数指针的类对象:类型转换运算符
  4. 类成员函数指针void(TC::*myfpoint)(int) = &TC::ptfunc;

20.7.2 std::function 可调用对象包装器
#

[!tip] std::function 用法

#include <functional>
std::function<void(int)> f1 = myfunc;       // 普通函数
std::function<int(int)> fs2 = TC::stcfunc;  // 静态成员函数
std::function<void(int)> f3 = tc3;          // 仿函数
  • ! std::function 不能包装类成员函数指针

20.7.3 std::bind 绑定器
#

[!tip] std::bind 用法

#include <functional>
auto bf1 = std::bind(myfunc1, 10, 20, 30);  // 绑定所有参数
bf1();  // 直接调用

auto bf2 = std::bind(myfunc1, placeholders::_1, placeholders::_2, 30);
bf2(5, 15);  // 部分参数在调用时指定

[!tip] std::bind 特性

  • 预先绑定的参数通过值传递
  • placeholders 传递的参数通过引用传递
  • 可以绑定成员函数和成员变量
CQ cq;
auto bf5 = std::bind(&CQ::myfunpt, &cq, placeholders::_1, placeholders::_2);
bf5(10, 20);  // 调用成员函数

std::function<int&(void)> bf7 = std::bind(&CQ::m_a, &cq);
bf7() = 60;  // 修改成员变量

20.8 lambda 表达式与 for_each、find_if 简介
#

  • @ lambda表达式图 ![[白板/P61 函数对象与可调用对象.canvas|函数对象与可调用对象]]

20.8.1 用法简介
#

[!tip] lambda 表达式一般形式

[捕获列表](参数列表) -> 返回类型 { 函数体; };

  • 返回类型必须后置,可省略(编译器自动推导)
  • 无参数时可省略 ( )
  • 捕获列表和函数体不能省略
auto f = [](int a) -> int { return a + 1; };
cout << f(1) << endl;  // 2

auto f2 = [] { return 2; };  // 无参数,返回类型省略

20.8.2 捕获列表
#

[!tip] 捕获列表规则

捕获方式含义
[]不捕获任何变量
[&]按引用捕获所有外部变量
[=]按值捕获所有外部变量
[this]捕获 this 指针
[变量名]按值捕获指定变量
[&变量名]按引用捕获指定变量
[=, &变量名]默认按值,指定按引用
[&, 变量名]默认按引用,指定按值

20.8.3 lambda 表达式延迟调用易出错细节
#

[!tip] 按值捕获的陷阱

int x = 5;
auto f = [=] { return x; };
x = 10;
cout << f() << endl;  // 5,不是 10
  • ==按值捕获时,变量值在 lambda 定义时已复制==
  • 想即时访问外部变量,用引用捕获 [&]

20.8.4 lambda 表达式中的 mutable
#

[!tip] mutable 关键字

  • 按值捕获的变量默认不能修改
  • mutable 允许修改按值捕获的变量副本
  • 使用 mutable 时,( ) 不能省略
auto f = [=]() mutable { x = 6; return x; };

20.8.5 lambda 表达式的类型和存储
#

[!tip] 闭包类型

  • lambda 表达式的类型称为闭包类型(Closure Type)
  • 每个 lambda 触发编译器生成一个独一无二的类类型
  • 可以用 std::functionstd::bind 保存

[!tip] 捕获列表为空的 lambda

  • 可转换为普通函数指针
using functype = int(*)(int);
functype fp = [](int tv) { return tv; };

20.8.6 lambda 表达式再演示
#

[!tip] for_each 中的 lambda

vector<int> myvector {10, 20, 30, 40, 50};
int isum = 0;
for_each(myvector.begin(), myvector.end(), [&isum](int val) {
    isum += val;
});

[!tip] find_if 中的 lambda

auto result = find_if(myvector.begin(), myvector.end(), [](int val) {
    return val > 15;
});

20.9 lambda 表达式捕获模式的陷阱分析
#

20.9.1 捕获列表中的 &
#

[!tip] 引用悬空问题

  • 按引用捕获会导致 lambda 包含绑定到局部变量的引用
  • 局部变量超出作用域后,引用失效
  • ==产生未定义行为==

20.9.2 形参列表可以使用 auto(C++14)
#

[!tip] auto 形参

gv.push_back([&, auto tv](int tv) {
    if (tv % tmpvalue == 0) return true;
    return false;
});

20.9.3 成员变量的捕获问题
#

[!tip] 成员变量不能被捕获

  • 捕获只针对非静态局部变量
  • [=] 捕获的是 this 指针,不是成员变量
  • ==lambda 的生命周期依赖于对象的生命周期==

[!tip] 解决方案

  • 将成员变量复制到局部变量
auto tmpvalueCopy = m_tmpvalue;
gv.push_back([tmpvalueCopy](auto tv) { ... });

20.9.4 广义 lambda 捕获(C++14)
#

[!tip] 广义捕获

gv.push_back([tmpvalue = m_tmpvalue](auto tv) {
    // tmpvalue 是副本
});

20.9.5 静态局部变量
#

[!tip] 静态变量不捕获但可使用

  • 静态局部变量不能被捕获,但可在 lambda 中使用
  • ==类似按引用捕获的效果==
  • 不同 lambda 在不同时机输出结果不同

20.10 可变参数函数、initializer_list 与省略号形参
#

20.10.1 可变参数函数
#

[!tip] 可变参数函数

  • 参数数量不固定的函数
  • 所有实参类型相同时用 initializer_list
  • 类型不同时用省略号形参(C时代产物)

20.10.2 initializer_list(初始化列表)
#

[!tip] initializer_list 特点

  • C++11 新类型,类模板
  • 元素永远是常量值,不能改变
  • begin()end() 遍历,size() 获取元素个数
  • 复制和赋值不复制元素(共享元素)
#include <initializer_list>
void printvalue(initializer_list<string> tmpstr) {
    for (auto& tmpitem : tmpstr) {
        cout << tmpitem << endl;
    }
}
printvalue({"aa", "bb", "cc"});

[!tip] 统一初始化

  • C++11 引入统一初始化(Uniform Initialization)
  • {} 初始化各种类型
  • 背后由 std::initializer_list 支持

20.10.3 省略号形参
#

[!tip] 省略号形参

  • C时代产物,C++中用得较少
  • 至少有一个有效形参
  • 只能出现在形参列表最后
  • 不进行类型检查
#include <cstdarg>
double average(int num, ...) {
    va_list valist;
    va_start(valist, num);
    double sum = 0;
    for (int i = 0; i < num; ++i) {
        sum += va_arg(valist, int);
    }
    va_end(valist);
    return sum / num;
}
  • ! 不建议在 C++ 中使用省略号形参

20.11 萃取技术概念与范例
#

  • @ 类型萃取图 ![[白板/P64 decltype与类型萃取.canvas|decltype与类型萃取]]

20.11.1 类型萃取简介
#

[!tip] 类型萃取(type traits)

  • 泛型编程技术,在 STL 实现源码中用得多
  • 编译期间的基于模板的接口
  • 用于查询或修改类型的属性
  • C++11 标准库提供很多类型萃取接口(类模板)

20.11.2 类型萃取范例
#

[!tip] 常用萃取接口

  • is_void<T>::value:是否是 void
  • is_class<T>::value:是否是类
  • is_object<T>::value:是否是对象类型
  • is_pod<T>::value:是否是 POD 类型
  • is_default_constructible<T>::value:是否有默认构造函数
  • is_copy_constructible<T>::value:是否有拷贝构造函数
  • is_move_constructible<T>::value:是否有移动构造函数
  • has_virtual_destructor<T>::value:是否有虚析构函数
#include <type_traits>
cout << is_void<int>::value << endl;        // 0
cout << is_class<string>::value << endl;    // 1
cout << is_pod<int>::value << endl;         // 1

20.11.3 迭代器萃取简介
#

[!tip] iterator_traits

  • 给定迭代器,萃取出迭代器的种类
  • 用于算法内部根据迭代器类型优化处理

20.11.4 总结
#

[!tip] 萃取技术总结

  • 属于泛型编程技术
  • STL 实现源码中用得多
  • 实际工作中应用较少(业务逻辑编码为主)
  • 能实现一些意想不到的效果
Reply by Email
C++ 新经典笔记 - 这篇文章属于一个选集。

相关文章