知识点#
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&&==- 必须同时满足两个条件:
- 必须是函数模板
- 必须发生模板类型推断,形参为
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 不推导为引用
- 实参是
const→const属性会成为 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;→x为intconst auto x2 = x;→x2为const intauto xy2 = xy;(xy为const int&) →xy2为int
[!tip] 指针或引用类型(非万能引用)
- ==不会抛弃
const,但会抛弃引用==auto& xy3 = xy;(xy为const int&) →xy3为const int&,auto为const intauto* xp2 = &x;→xp2为int*
[!tip] 万能引用类型
auto&& wnyy0 = 222;→ 右值,wnyy0为int&&auto&& wnyy1 = x;→ 左值,发生引用折叠,wnyy1为int&
20.5.2 auto 类型针对数组和函数的推断#
[!tip] 数组推断
auto myarr = mystr;→myarr为char const*(指针)auto& myarr2 = mystr;→myarr2为char const(&)[14](数组引用)
[!tip] 函数推断
auto tmpf = myfunc3;→ 函数指针auto& tmpf2 = myfunc3;→ 函数引用
20.5.3 auto 类型 std::initializer_list 的特殊推断#
[!tip] initializer_list 特殊推断
auto x3 = {30};→x3为std::initializer_list<int>auto x4{30};→x4为int(无等号)- ==只有
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;(i为const int) →j2为const intdecltype(iy) j3 = j2;(iy为const int&) →j3为const int&
[!tip] decltype 后是表达式
decltype(i + 1) j;→j为intdecltype(*pi) k3 = i;→k3为int&(能做左值的表达式返回引用)decltype((i)) iy3 = i;→iy3为int&(双层括号变成表达式)
[!tip] decltype 后是函数
decltype(testf()) tmpv = 1;→tmpv为int(函数返回类型)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] 可调用对象分类
- 函数指针:
void(*paf)(int) = myfunc;- 仿函数:重载
operator()的类对象- 可转换为函数指针的类对象:类型转换运算符
- 类成员函数指针:
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::function和std::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:是否是voidis_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 总结#
Reply by Email[!tip] 萃取技术总结
- 属于泛型编程技术
- STL 实现源码中用得多
- 实际工作中应用较少(业务逻辑编码为主)
- 能实现一些意想不到的效果

