聊一聊C++设计模式、函数式编程等
文章目录
1.variant技巧
自定义match模板函数可以将多个lamda打包成一个,通过重载来调用
#include <iostream>
#include <variant>
template <class ...Fs>
struct match: protected Fs...{
explicit match(Fs const&... fs)
:Fs(fs)... {}
using Fs::operator()...;
};
template <class ...Fs>
match(Fs const&... fs) -> match<Fs...>;
int main()
{
std::variant<float, int, std::string> v;
v = "abcd";
std::visit(match[&](auto x){
std::cout<<x<<std::endl;}, [&](std::string x){std::cout<<x<<std::endl;},v)
return 0;
}
- 测试:

也可以利用C++17 新增的 overloaded 模板,可以直接生成匿名访问器,简化代码, 下面的代码是等价的
int main() {
std::variant<int, float, string> value = 1.0;
std::visit(overloaded{
void operator()(int i) { cout << "int: " << i << '\n'; }
void operator()(float f) { cout << "float: " << f << '\n'; }
void operator()(const std::string& s) { cout << "str: " << s << '\n'; }
},value);
}
用static可以检测实例化了几次
#include <iostream>
#include <variant>
template <class ...Fs>
struct match: protected Fs...{
explicit match(Fs const&... fs)
:Fs(fs)... {}
using Fs::operator()...;
};
int main()
{
auto func = [&](auto val){
static int _=printf("instance\n");
std::cout<<val>>std::endl;
};
func(1);
func(2);
func(3.14f);
return 0;
}
可以在编译器进行判断
- 编译器可以进行优化
#include <iostream>
#include <variant>
template <class ...Fs>
struct match: protected Fs...{
explicit match(Fs const&... fs)
:Fs(fs)... {}
using Fs::operator()...;
};
int main()
{
auto func = [&](auto val){
static int _=printf("instance\n");
if constexpr (val)
std::cout << "true" <<std::endl;
else
std::cout << "false" <<std::endl;
};
func(std::integral_constant<bool,false>{});//等价于std::false_type
func(std::integral_constant<bool,true>{});//等价于std::true_type
return 0;
}
- std::integral_constant
- 测试:实例化了两次

运行期判断转变成编译器判断
#include <iostream>
#include <variant>
template <class ...Fs>
struct match: protected Fs...{
explicit match(Fs const&... fs)
:Fs(fs)... {}
using Fs::operator()...;
};
//bool转std::variant
//编译器就知道是true类型,false类型
std::variant<std::false_type, std::true_type>
bool_variant(bool x)
{
if (x)
return std::true_type{};
else
return std::false_type{};
}
void saxpy(std::vector<float> a, float b, float c)
{
auto has_b = bool_variant(b!=1);
auto has_c = bool_variant(c!=1);
std::visit([&](auto has_b, auto has_c){
for (auto& ai: a)
{
//编译器会自动产生4个分支
if constexpr (has_b)
ai* = b;
if constexpr (has_c)
ai+ = c;
}
})
}
int main()
{
std::vector<float> a(1024);
saxpy(a,1,0);
saxpy(a,1,3.14f);
return 0;
}
- 参考:std::variant 与 std::visit
- 进一步:有很多静态多态的小工具

2.std::enable_if_t用法
(1)C++模板 SFINAE
SFINAE: Substitution Failure Is Not An Error (替换失败不是错误)
SFINAE 可用于模板类型参数 和 返回值类型 两个地方,
在这两个地方做类型不正确的操作 它不会报错和退出程序, 而是跳过当前匹配去尝试其他匹配.
- eg:函数体内做类型不正确的操作, 会报错
#include <iostream>
using std::cout;
using std::endl;
struct book {
// 重新定义一个类型, 这个类型叫做 page_type
// page_type 属于 book 作用域中的一个成员.
typedef int page_type;
// 使用另外一种语法来重新定义一个类型, 这个类型叫做 name_type,
// name_type 属于 book 作用域中的一个成员.
using name_type = string;
};
int main(void) {
// 使用book结构中的类型别名来定义变量.
book::page_type a = 1099;
book::name_type b = "C++ Standard Library";
// author_type 不存在于 book 结构中, 因此会报错.
book::author_type c = "Nicolai M.Josuttis"; // error.
// output:
// 'author_type': is not a member of 'book'
return 0;
}
- eg:模板尖括号里做类型不正确的操作, 不会报错.
#include <iostream>
#include <string>
using std::cout;
using std::endl;
struct book
{
typedef int page_type;
using name_type = std::string;
};
// T::author_type 是一个不存在的东西, 在这里叫做替换失败, 不是类型操作错误.
// 当替换失败时, 编译器会跳过这个匹配, 继续寻找下一个模板函数.
template <typename T, class U = typename T::author_type>
void example(T t)
{
cout << "template<typename T = book::author_type > void example();" << endl;
}
template <typename T>
void example(T t)
{
cout << "void example(book b)" << endl;
}
int main(void)
{
book b;
example(b);
return 0;
}
(2)C++模板 匿名类型参数
模板的类型参数, 除了能够定义默认类型之外.
还可以定义匿名类型参数, 这个匿名类型参数不能被函数体使用, 也不能被返回值使用.
匿名类型参数利用替换失败不是错误的特性来判定, 如果没有出现替换失败, 那么就是符合匹配的.
什么场景下需要用到匿名类型参数?
- 以上面的eg为例,这个函数声明了 class U 这个类型参数, 并且设定了一个默认值T::author_type,
但是函数体、返回值中都没有使用到 U 这个类型, 如果实际项目中函数体的代码非常复杂,
多出一个不明确的类型是非常恐怖的一件事情, 而匿名类型参数可以减少这个心里负担.
template<typename T, class U = typename T::author_type>
void example(T t) {
cout << "template<typename T = book::author_type > void example();" << endl;
}
所以, 匿名类型参数就是用来降低心里复杂度的蜜枣.
- 由于 struct book 没有定义 author_type 这个成员类型, 因此 T::author_type 是一个无效的调用, 编译器不会报错, 而是选择跳过当前匹配进而去寻找其他匹配.
#include <iostream>
#include <string>
using std::cout;
using std::endl;
struct book
{
typedef int page_type;
using name_type = std::string;
};
// class = typename T::author_type 就是一个匿名类型参数.
template <typename T, class = typename T::author_type>
void example(T t)
{
cout << "template<typename T = book::author_type > void example();" << endl;
}
// template<typename T, class = typename T::page_type>
// 等同于
// template<typename T, typename = int>
// 第二个类型参数是一个匿名类型参数(没有提供一个identifier), 并且提供了一个int作为默认类型.
template <typename T, class = typename T::page_type>
void example(T t)
{
cout << "void example(book b)" << endl;
}
int main(void)
{
book b;
example(b);
return 0;
}
模板参数的定义方式
什么是 Identifier?
template <typename T> // T 就是 Identifier.
void example() {};
Identifier 的命名不能跟 C++ 中的关键字出现冲突.
// 错误的
// 因为 identifier 占用和覆盖了 int , 会导致后续功能不正常, 因此编译是不会通过的.
template<typename int>
void normal(int s){
cout << "normal: " << s << endl;
}
// 正确的
// 但是建议使用常见的 T 或者 U 来当作 Identifier.
template<typename aaa>
void normal(aaa s){
cout << "normal: " << s << endl;
}
template <> 中 typename 和 class 有什么区别?
没有区别, 但是建议统一风格, 只使用其中任意一种,
我个人基本都是使用typename.
typename 这个关键字是什么含义?
它是一个标识符, 即便不放在 template <> 中, 也可以在常规的函数体中使用, 例如: typename int a = 10;
但是在 template<> 中, 要声明一个类型, 必须要使用 typename 或 class 标识符, 用来标识它是一个Identifier.
- eg:
#include <iostream>
using std::cout;
using std::endl;
// 具体化
// int 是一个具体的类型
// n 是一个Identifier 也是一个具体值, 所以不能用 n 来声明一个变量.
template <int n> //
void specialization()
{
cout << n << endl;
};
// 具体化 和 匿名类型参数
// 因为 int 是一个具体类型, 所以不能也没必要定义一个 Identifier , 所以函数体无法使用这个参数.
// 匿名类型参数必须要有一个默认值.
template <int = 0> //
void specialization_and_anonymous()
{
cout << "specialization_and_anonymous" << endl;
};
int main(void)
{
specialization<10>(); // 必须这样使用.
specialization_and_anonymous();
// output:
// 10
// specialization_and_anonymous
return 0;
}
//typename和匿名类型
template <typename T, typename = bool>
T example(T t)
{
cout << "template<typename T = book::author_type > void example();" << endl;
return t;
}
(3)enable_if
模板的主要任务就是推导/替换, 将调用时传递的具体值推导出其类型, 然后将 T 替换成该值的类型.
-
模板支持推导和替换任意类型参数, 但每种类型支持的操作并非都相同, 因此一份模板函数不可能解决所有情况.
-
当一份标准模板函数无法解决特定情况时,按条件匹配进入特定模板函数就变成了不可或缺.
enable_if 利用了模板匹配的技巧和struct结构, 巧妙的将条件匹配分割成两种情况,
一种是true的情况: 为结构绑定一个type
一种是false的情况: 采取留空策略
template <bool _Test, class _Ty = void>
struct enable_if {}; // no member "type" when !_Test
// 这个实现叫做部分具体化(partial specialization), 即: 第一个参数 (<true>) 采用具体值.
// 这个模板函数简要但是浓缩了几个规则:
// 当调用时传递的参数是true时, 一定会进入这个函数.
// 当调用时传递的参数是true且不提供其他参数时, <class _Ty> 会把自动合并上面一个enable_if的 <class _Ty = void>;
// 当调用时传递的参数是true且提供其他参数时, _Ty 会替换成传递参数的类型(放弃void).
template <class _Ty>
struct enable_if<true, _Ty> { // type is _Ty for _Test
using type = _Ty;
};
- eg:
#include <iostream>
#include <type_traits>
using std::cout;
using std::endl;
//template <typename T, std::enable_if<true, int>::type = 0>也可以编译通过
template <typename T, typename std::enable_if<true, int>::type = 0>
T example(T t)
{
cout << "template<typename T = book::author_type > void example();" << endl;
return t;
}
int main(void)
{
example(10);
return 0;
}
enable_if<is_integral::value, int> 等同于 enable_if<true, int> 或者 enable_if<false, int>,如果第一个参数是 true , 那么 enable_if 这个结构就有一个 type 成员, 这时调用 enable_if::type 就是OK的.
如果第一个参数是 false, 那么 enable_if 这个结构就没有一个 type 成员, 这时调用 enable_if::type 就是失败的, 但是不会报错而是跳过当前匹配.
enable_if 的第二个参数提供了一个int, 所以 enable_if::type 其实就是一个 int, 也就是说
<typename T, typename enable_if<is_integral<T>::value, int>::type = 0> 等同于
<typename T, typename int = 0> 等同于
<typename T, int = 0>
(4)enable_if_t
它基于模板的 SFINAE 和 匿名类型参数 的基础概念上进行了简洁且完美的封装(落地).
enable_if_t 强制使用 enable_if 的 ::type 来触发 SFINAE 规则, 如果失败则跳过当前匹配进入下一个匹配.
template <bool _Test, class _Ty = void>
using enable_if_t = typename enable_if<_Test, _Ty>::type;
enable_if_t 节省掉了 typename 和 ::type , 是因为它背后使用 using 帮我们多写了typename 和 ::type (即: 强行重定义了类型).
- eg:
#include <iostream>
#include <type_traits>
using std::cout;
using std::endl;
// 省略了 typename
// 省略了 ::type
//template <typename T, typename std::enable_if_t<std::is_integral<T>::value, int> = 0>也可以编译通过
template <typename T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
void example(T t)
{
cout << "template<typename T = book::author_type > void example();" << endl;
}
int main(void)
{
example(10);
return 0;
}
(5)后面那个 = 0 是个什么东西?
由于 enable_if 第二个类型设定了默认为 void 类型
- 模板的非类型参数不能用void,如果使用void会报错
- 因此 typename enable_if::void = 0(typename enable_if::void = nullptr) 是失败的, 它会导致程序跳过这个匹配.
- eg:
#include <iostream>
#include <type_traits>
using std::cout;
using std::endl;
template <typename T, typename std::enable_if<true>::type = nullptr>
T example(T t)
{
cout << "template<typename T = book::author_type > void example();" << endl;
return t;
}
int main(void)
{
example(10);
return 0;
}
- 测试:

由于 enable_if 或 enable_if_t 都是采取 匿名类型参数 形式来完成判断行为。
- 由于 匿名类型参数 必须要有一个默认值
- 因此 大部分的STL、TR1 的代码都会在使用 enable_if 或者 enable_if_t 时, 为第二个类型提供一个 int, 然后设定一个 0 为默认值.
- eg:
#include <iostream>
#include <type_traits>
using std::cout;
using std::endl;
template <typename T, typename std::enable_if<true, int>::type>
T example(T t)
{
cout << "template<typename T = book::author_type > void example();" << endl;
return t;
}
int main(void)
{
example(10);
return 0;
}
-
测试:

-
eg:
//不是int类型
template <class T, std::enable_if_t<!std::is_interal_v<T>, int> = 0>
void func(T const& t)
{
std::cout<< t <<std::endl;
}
//是int类型
template <class T, std::enable_if_t<std::is_interal_v<T>, int> = 0>
void func(T const& t)
{
std::cout<<"is intarage: "<<t<<std::endl;
}
int main()
{
func(23);
func('c');
func(3.14f);
}
- 测试:

3.PIMPL模式
目的:由于具体的实现在Course.cpp中,所以在给Course( Course::Impl)增加属性的时候,main.cc是不会重新编译的
//Course.h
#pragma once
#include <memory>
struct Course{
struct Impl;
std::shared_ptr<Impl> impl;
Course();
~Course();
void func();
};
//Course.cc
#include "Course.h"
struct Course::Impl
{
int x, y;
void func(){
x+=1;
}
};
Course::Course():impl(std::make_shared<Impl>()) {}
Course::~Course() = default;
void Course::func()
{
impl->func();
}
目的:传值进行浅拷贝,因为Course本身就是一个指针(内部有智能指针)
//main.cc
#include "Course.h"
void func(Course course)
{
course.func();
}
int main()
{
Course co;
func(co);
return 0;
}
4.二维数组
//不好的做法
constexpr int N = 32;
struct Grid{
vector<int> p;
size_t nx;
Grid(size_t x, size_t y)
: p(x * y), nx(x) {}
int& operator()(size_t x,size_t y)
{
return p[x*nx+y];
}
};
int java_main()
{
int** p = new int*[N];
for (int i = 0; i<N;++i)
{
p[i] = new int[N];
}
for (int i = 0; i<N;++i)
{
for (int j = 0; j<N;++i)
{
p[i][j] = 1;
}
}
for (int i=0; i< N;++i)
{
delete[] p[i];
}
delete[] p;
return 0;
}
//好的做法
int main()
{
//等价于vector<int> p(N*N);
int*p = new int [N*N];
for (int i = 0; i<N;++i)
{
for (int j = 0; j<N;++i)
{
p[i*N+j]=1;
}
}
delete[] p;
//更近一步
Grid g(N, N);
for (int j = 0; j < N; j++) {
for (int i = 0; i < N; i++) {
g(i, j) = 1;
}
}
return 0;
}
测试访问效率
- eg:my_course/course/tools/POC/04/grid.cpp
#include <iostream>
#include <cstdlib>
#include <vector>
#include <cmath>
#include <execution>
#include "ticktock.h"
#include <chrono>
#define TICK(x) auto bench_##x = std::chrono::steady_clock::now();
#define TOCK(x) std::cout << #x ": " << std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - bench_##x).count() << "s" << std::endl;
constexpr int N = 1<<12;
struct Grid {
std::vector<int> p;
size_t nx;
Grid(size_t nx, size_t ny)
: p(nx * ny), nx(nx)
{
}
int &operator()(size_t x, size_t y) {
return p[y * nx + x];
}
};
int main() {
TICK(java);
int **p = new int *[N]{};
for (int i = 0; i < N; i++) {
p[i] = new int[N]{};
}
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
p[std::rand() % N][std::rand() % N] += 1;
}
}
for (int i = 0; i < N; i++) {
delete[] p[i];
}
delete[] p;
TOCK(java);
TICK(cpp);
Grid g(N, N);
for (int j = 0; j < N; j++) {
for (int i = 0; i < N; i++) {
//%N防止越界
g(std::rand() % N, std::rand() % N) += 1;
}
}
TOCK(cpp);
return 0;
}
5.折叠表达式
-
eg:my_course/course/tools/POC/04/test.cpp
#include <iostream>
#include <cstdlib>
#include <vector>
#include <cmath>
#include <numeric>
#include <algorithm>
template<class ...T>
auto sum(T ...t)
{
return (0+...+t);//sum()也可以运行,return (+...+t)也行,但是sum()中必须有值才行
}
int main()
{
std::cout<<sum(1,2,3)<<std::endl;
}
- eg:course/tools/POC/04/main.cpp
#include <iostream>
#include <cstdlib>
#include <vector>
#include <cmath>
#include <numeric>
#include <algorithm>
template <class ...T>
constexpr auto sum(T ...t) {
return (sizeof(T) + ...);
}
template <class T0, class ...T>
void print(T0 const &t0, T const &...t) {
std::cout << t0;
((std::cout << ' ' << t), ...);
std::cout << std::endl;
}
template<typename... Ts>
void printAll(Ts&&... mXs)
{
(std::cout << ... << mXs) << std::endl;
}
void func2(int const &t) {
printf("int const &\n");
}
void func2(int &&t) {
printf("int &&\n");
}
//std::forward作用:
//当T为左值引用类型时,t将被转换为T类型的左值
//当T不是左值引用类型时,t将被转换为T类型的右值
template <class ...T>
void func1(T &&...t) {
func2(std::forward<decltype(t)>(t)...);//等价于: func2(std::forward<(T)>(t)...);
}
int main() {
printAll(1,2,3);
float arr[1<<20]; int i = 0;
std::generate_n(std::begin(arr), std::size(arr), [&] () { return std::sin(i++); });
float sum = std::accumulate(std::begin(arr), std::end(arr), 0.f, std::minus{});
print(sum);
//std::reduce可以reduce一个vector的数组
std::vector<float> arr;
float sum = std::reduce(arr.begin().arr.end());
//为了支持vector和C数组,可以使用std::begin().std::end(), std::reduce();来自C++17
float arr[32] = {1,2,3};
//等价于float sum = std::reduce(std::begin(arr), std::end(arr),0.f, std::plus<float>{});//std::plus{};是针对所有类型的加法
float sum = std::reduce(std::begin(arr), std::end(arr),0.f, [](float a, float b){return a+b;});
// std::reduce 比std::accumulate更准确而已
float arr2[1<<20];
std::generate(std::begin(arr),std::end(arr),std::rand);
return 0;
}