C++_chapter15_C++重要知识点_auto,function,bind,decltype

本文内容:auto,function,bind,decltype,lambda,initializer_list

文章目录

  • [第15章 C++重要知识点](#第15章 C++重要知识点)
    • [15.5 理解auto类型推断,auto应用场合](#15.5 理解auto类型推断,auto应用场合)
      • [15.5.1 auto 类型常规推断](#15.5.1 auto 类型常规推断)
        • [15.5.1.1 传值方式(非指针非引用)](#15.5.1.1 传值方式(非指针非引用))
        • [15.5.1.2 传递指针和引用 但不是万能引用](#15.5.1.2 传递指针和引用 但不是万能引用)
        • [15.5.1.3 auto 与 万能引用](#15.5.1.3 auto 与 万能引用)
      • [15.5.2 auto 类型针对数组和函数的推断](#15.5.2 auto 类型针对数组和函数的推断)
      • [15.5.3 auto std::initializer_list 特殊推断](#15.5.3 auto std::initializer_list 特殊推断)
      • [15.5.4 auto 不适用场景](#15.5.4 auto 不适用场景)
      • [15.5.5 使用场合](#15.5.5 使用场合)
        • [5.5.6.1 在stl中使用](#5.5.6.1 在stl中使用)
        • [5.5.6.2 在模板的返回值中使用](#5.5.6.2 在模板的返回值中使用)
    • [15.6 详解decltype含义,decltype主要用途](#15.6 详解decltype含义,decltype主要用途)
      • [15.6.1 decltype 作用和举例](#15.6.1 decltype 作用和举例)
        • [15.6.1.1 decltype 后面圆括号是变量](#15.6.1.1 decltype 后面圆括号是变量)
        • [15.6.1.2 decltype 后面的括号中是非变量(表达式)](#15.6.1.2 decltype 后面的括号中是非变量(表达式))
        • [15.6.1.3 decltype后圆括号中是函数](#15.6.1.3 decltype后圆括号中是函数)
      • [15.6.2 decltype 主要用途,C++11方式](#15.6.2 decltype 主要用途,C++11方式)
        • [15.6.1.1 用途1:应付可变类型](#15.6.1.1 用途1:应付可变类型)
        • [15.6.1.2 decltype 与 临时对象的使用](#15.6.1.2 decltype 与 临时对象的使用)
        • [15.6.1.3 通过变量表达式抽取变量类型](#15.6.1.3 通过变量表达式抽取变量类型)
        • [15.6.1.4 decltype 与 auto构成后置语法](#15.6.1.4 decltype 与 auto构成后置语法)
        • [15.6.1.5 decltype(auto) 方式](#15.6.1.5 decltype(auto) 方式)
          • [1 decltype(auto) 在函数中使用](#1 decltype(auto) 在函数中使用)
          • [2 decltype(auto) 在变量中使用](#2 decltype(auto) 在变量中使用)
    • 15.7可调用对象、std::function、std::bind
      • [15.7.1 可调用对象](#15.7.1 可调用对象)
        • [15.7.1.1 函数指针](#15.7.1.1 函数指针)
        • [15.7.1.2 具有operator() 成员函数的类对象(仿函数,函数对象)](#15.7.1.2 具有operator() 成员函数的类对象(仿函数,函数对象))
        • [15.7.1.3 可被转换为函数指针的函数对象](#15.7.1.3 可被转换为函数指针的函数对象)
        • [15.7.1.4 类成员函数指针](#15.7.1.4 类成员函数指针)
        • [15.7.1.5 总结](#15.7.1.5 总结)
      • [15.7.2 std::function 可调用对象包装器](#15.7.2 std::function 可调用对象包装器)
        • [15.7.2.1 function绑定普通函数](#15.7.2.1 function绑定普通函数)
        • [15.7.2.2 function绑定类的静态成员函数](#15.7.2.2 function绑定类的静态成员函数)
        • [15.7.2.3 function绑定仿函数](#15.7.2.3 function绑定仿函数)
        • [15.7.2.4 使用示例](#15.7.2.4 使用示例)
      • [15.7.3 std::bind 绑定器](#15.7.3 std::bind 绑定器)
        • [15.7.3.1 bind 占位符](#15.7.3.1 bind 占位符)
        • [15.7.3.2 bind 值传递](#15.7.3.2 bind 值传递)
        • [15.7.3.3 绑定成员函数](#15.7.3.3 绑定成员函数)
        • [15.7.3.4 绑定类的对象](#15.7.3.4 绑定类的对象)
      • [15.7.4 function 和 bind 结合使用](#15.7.4 function 和 bind 结合使用)
        • [示例1: function + bind 结合使用](#示例1: function + bind 结合使用)
        • [示例2: 使用function+map实现 switch功能](#示例2: 使用function+map实现 switch功能)
        • [示例3: function的实现原理](#示例3: function的实现原理)
        • [示例4: bind和function实现线程池](#示例4: bind和function实现线程池)
      • [15.7.5 总结](#15.7.5 总结)

第15章 C++重要知识点

15.5 理解auto类型推断,auto应用场合

auto 和模板推倒有点相似,就是将T 自动推断为auto。

auto的特点如下:

特点1:auto的自动类型推断发生在编译期间;

特点2: auto定义变量必须立即初始化,这样编译器才能推断出它的实际类型。编译的时候才能确定auto的类型和整个变量的类型,然后在编译期间就可以用真正的类型替换掉auto这个类型占位符。

特点3:auto的使用比较灵活,可以和指针、引用、const等限定符结合使用。

15.5.1 auto 类型常规推断

auto C++11的产物,auto用于变量的自动类型推断,在声明变量时候根据变量初始化类型自动为此变量选择匹配的类型,而不需要我们显示制定。

15.5.1.1 传值方式(非指针非引用)

auto后边直接跟上变量名,叫传值方式。

cpp 复制代码
	void test()
	{
		auto x = 25;
		const auto x2 = x;  // const int, auto 是 int 
		const auto& xy = x; // xy = const int & ,auto 还是 int类型。
		auto xy2 = xy;  // xy2 = int , 因为在传值时,引用和const 会被抛弃,xy本来是 const int & 类型,但是const 和 &都被抛弃了。
	}

验证:

cpp 复制代码
void test()
{
  int x = 25;
  const int x2 = x;
  const int & xy = x;
  int xy2 = xy; // xy本来是 const int & 
}
15.5.1.2 传递指针和引用 但不是万能引用

auto 与指针和引用的结合。

实验1:向模板中传递 左值。

cpp 复制代码
	template<typename T>
void tf(const T & tmprv)
{
}

void test()
	{
		auto x = 25;  		// auto = int ; int x = 25;
		const auto x2 = x;  // auto = int, const int x2 
		const auto& xy = x; // xy = const int & 
		auto xy2 = xy;  // xy2 = int , 因为在传值时,引用& 和const 会被抛弃;  int xy2 = xy;

		tf(x);
	}

推断:

cpp 复制代码
template<typename T>
void tf(const T & tmprv)
{
}

/* First instantiated from: insights.cpp:18 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void tf<int>(const int & tmprv)
{
}
#endif


void test()
{
  int x = 25;
  const int x2 = x;
  const int & xy = x;
  int xy2 = xy;
  tf(x);
}

分析:向模板中传递左值,const T & tmprv 被推断出来的仍然是左值:const int & tmprv;

2 :向模板传递const int 类型,得到的结果就是const auto & x2 ,

cpp 复制代码
	template <typename T>
	void tf(const T& tmprv)
	{

	}

	void test()
	{
		auto x = 25;
		const auto x2 = x;  //auto是int,整理 const int x2 
		const auto& xy = x; // xy = const int & 
		auto xy2 = xy;  // xy2 = int , 因为在传值时,引用和const 会被抛弃;
		//tf(x);
		tf(x2);
		//tf(xy);
	}

推导:

cpp 复制代码
template<typename T>
void tf(const T & tmprv)
{
}

/* First instantiated from: insights.cpp:19 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void tf<int>(const int & tmprv)
{
}
#endif

void test()
{
  int x = 25;
  const int x2 = x;
  const int & xy = x;
  int xy2 = xy;
  tf(x2);
}

分析,向模板传递的是 const int & , T 被推断为 int类型。

3:传递const + 引用, const int &

cpp 复制代码
	template <typename T>
	void tf(const T& tmprv)
	{

	}

	void test()
	{
		auto x = 25;
		const auto x2 = x;  // const int
		const auto& xy = x; // xy = const int & 
		auto xy2 = xy;  // xy2 = int , 因为在传值时,引用和const 会被抛弃;
		//tf(x);
		//tf(x2);
		tf(xy);
	}

推导:

cpp 复制代码
template<typename T>
void tf(const T & tmprv)
{
}

/* First instantiated from: insights.cpp:20 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
void tf<int>(const int & tmprv)
{
}
#endif


void test()
{
  int x = 25;
  const int x2 = x;
  const int & xy = x;
  int xy2 = xy;
  tf(xy);
}

分析:推倒出来的结果也是 const T & tmprv 被推断出const int & tmprv

4:auto推断指针

cpp 复制代码
	void test()
	{
		auto x = 25;
		const auto x2 = x;  // const int
		const auto& xy = x; // xy = const int & 
		auto xy2 = xy;  // xy2 = int , 因为在传值时,引用和const 会被抛弃;
		auto xy3 = xy;
		auto y = new auto(100);// auto y 被推断出 int * ,  int * y = new int{100};
		const auto* xp = &x; // auto被推倒为 int ,x是引用 & 
		auto* xp2 = &x;
		auto xp3 = &x;
	}

验证:

cpp 复制代码
void test()
{
  int x = 25; // auto x , 推断为 int x ,
  const int x2 = x; // x 是 int ,推断出来的也是:auto是 int 
  const int & xy = x;// x时候 const int ,xy 是 int ,x中的 const 背会丢弃掉。 
  int xy2 = xy;
  int xy3 = xy; // 传递 const int & , xy2 仍然是 int类型,const 和 & 被丢弃。

  int * y = new int{100};
  const int * xp = &x;
  int * xp2 = &x;
  int * xp3 = &x;
}
15.5.1.3 auto 与 万能引用

auto 与 && 完成引用时,也会发生引用折叠现象。

cpp 复制代码
	void test2()
	{
		auto&& wn = 12;
		auto&& wn3 = wn;// && + & = & 所以 wn3是左值引用。
	}

推导:

cpp 复制代码
void test2()
{
  int && wn = 12;
  int & wn3 = wn;
}

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

C++中数组引用的写法,mystr2 是对 mystr 的引用。

cpp 复制代码
		const char mystr[] = "I Love China";
		const char(&mystr2)[13] = mystr;   // 数组的引用
		cout << "mystr2[0] " << mystr2[0]; // 输出 I


	void test()
	{
		const char mystr[] = "I Love China";
		const char(&mystr2)[13] = mystr;   // 数组的引用
		cout << "mystr2[0] " << mystr2[0]; // 输出 I 

		int a[2] = { 1,2 };
		auto aauto = a; 
		auto& att = a;
	}

auto 自动类型推倒结果:

cpp 复制代码
void test()
{
  const char mystr[13] = "I Love China";
  const char (&mystr2)[13] = mystr;
  std::operator<<(std::operator<<(std::cout, "mystr2[0] "), mystr2[0]);
  int a[2] = {1, 2};
  int * aauto = a;
  int (&att)[2] = a;
}

auto 与 函数的结合:

cpp 复制代码
	void test()
	{
		const char mystr[] = "I Love China";
		const char(&mystr2)[13] = mystr;   // 数组的引用
		cout << "mystr2[0] " << mystr2[0]; // 输出 I 

		int a[2] = { 1,2 };
		auto aauto = a;
		auto& att = a;

		// 函数的 auto 
		auto pf = myfunc;
		pf(2);				// 方式1:函数的指针方式

		auto& pf2 = myfunc;	// 方式2:引用方式
		pf2(2);

		using FuncPtr_21 = void (*)(int);  // 方式1的推倒:定义一个函数指针类型,指向了 myfunc 函数,这个指针也可以指向不同的类型;
		FuncPtr_21 pf = myfunc;
		pf(2);

		void(&pf2)(int) = myfunc;	// 方式2的推倒:定义一个函数引用,该是myfunc的别名,只能指向myfunc函数,不能再指向其他函数了。
		pf2(2);
	}

15.5.3 auto std::initializer_list 特殊推断

当auto 遇到={}时候,推导就是initialist_list ,这是一种特殊的推导

cpp 复制代码
	auto x5 = { 1 };			// x5=	class std::initializer_list<int>
	void test()
	{
		// C++98 写法
		int x = 10;
		int x2(20);

		// C++11 写法
		int a = { 30 };
		int b{ 20 };

		// 改为 auto 
		auto x1 = 10;
		auto x21(20);
		auto a1 = { 30 };
		auto b1{ 30 };

	}

自动推导为:

cpp 复制代码
void test()
{
  int x = 10;
  int x2 = 20;
  int a = {30};
  int b = {20};
  int x1 = 10;
  int x21 = 20;
  std::initializer_list<int> a1 = std::initializer_list<int>{30};
  int b1 = {30};
}

当auto 遇到 ={} 这种语法时,会将推断为std::initializer_list数组,然后遇到{}中的类型后,再次推断std::initializer_list 数组的类型,比如{30} 推断数组类型为整型。

记忆方式:={} 这种是隐士类型转换,auto 遇到这种隐士类型转换就推断为了init数组。

15.5.4 auto 不适用场景

1 auto 不能用与函数参数,比如 void func(auto x, int y)

2 auto 在类中定义成员变量

cpp 复制代码
	class CT
	{
	public:
		auto m_i = 12;  // 语法不支持
		static const auto m_si = 12; // 可以
	};

15.5.5 使用场合

5.5.6.1 在stl中使用

auto在迭代器中使用。

cpp 复制代码
	std::map<string, int> mymap;
	mymap.insert({ "aa",1 });
	mymap.insert({ "bb",2 });
	mymap.insert({ "cc",3 });

	for (auto iter = mymap.begin(); iter != mymap.end(); ++iter)
	{
		cout << iter->first << "	" << iter->second << endl;
	}
5.5.6.2 在模板的返回值中使用

根据T推导出 类中成员函数的返回值。

cpp 复制代码
	class A
	{
	public:
		static int testr()
		{
			return 0;
		}
	};

	class B
	{
	public:
		static double testr()
		{
			return 10.5;
		}
	};

	template <typename T>
	auto ftestclass()
	{
		auto value = T::testr();
		return value;
	}
	void test3()
	{
		ftestclass<A>();
	}

推导:

cpp 复制代码
class A
{
  
  public: 
  static inline int testr()
  {
    return 0;
  }
  
};


class B
{
  
  public: 
  static inline double testr()
  {
    return 10.5;
  }
  
};


template<typename T>
auto ftestclass()
{
  auto value = T::testr();
  return value;
}

#ifdef INSIGHTS_USE_TEMPLATE
template<>
int ftestclass<A>()
{
  int value = A::testr();
  return value;
}
#endif


void test3()
{
  ftestclass<A>();
}

15.6 详解decltype含义,decltype主要用途

15.6.1 decltype 作用和举例

decltype 对于一个给定的变量名和表达式,能够推倒出名字或者表达式的类型。

decltype和auto有类似之处,两者都是用来推断类型的。

decltype有如下特点:

 decltype的自动类型推断也发生在编译期,这一点和auto一样。

 decltype不会真正计算表达式的值。

 const限定符、引用属性等有可能会被auto抛弃,但decltype 一般不会抛弃任何东西。

15.6.1.1 decltype 后面圆括号是变量
cpp 复制代码
	const int i = 0;
	const int &iy = i;
	auto j1 = 1;	// 传递方式推断,引用& 和const 属性都会被抛弃。
	decltype(i) j2 = 15;	// j2 = const int , 如果decltype 中是个变量,则变量的const 属性会返回。
	decltype(iy) j3 = j2;	// j3 = const int & ,

测试代码:

cpp 复制代码
	class CT
	{
	public:
		int i;
		int j;
	};
	void test()
	{
		decltype(CT::i) a;  // int 
		CT temp;		// CT 
		decltype(temp) temp2;	// CT 
		decltype(temp.i) mv = 2; // int 

	}
15.6.1.2 decltype 后面的括号中是非变量(表达式)

测试:

cpp 复制代码
	void  test2()
	{
		decltype(8) kkk = 2;
		int i = 0;
		int* pi = &i;
		int& iy = i;
		decltype(iy + 1) j; // iy + 1 得到的是一个整型,所以推断出int
		decltype(pi) k;// k 是 int *,因为 pi类型是int *;
		*pi = 4;
		decltype(i) k2;
		decltype(*pi) k3 = i;// 
	}

编译器推导

cpp 复制代码
void test2()
{
  int kkk = 2;
  int i = 0;
  int * pi = &i;
  int & iy = i;
  int j;
  int * k;
  *pi = 4;
  int k2;
  int &k3 = i;
}

decltype(*pi) 为什么推断为 int & ?

1 因为指针指向了对象,且这个对象可以被赋值,所以是左值。

2 如果表达式结果作为赋值语句左侧的值,那么decltype 返回的就是一个引用。

另一种得到左值的方式:双层括号。

cpp 复制代码
	// 另一种得到左值的方式
		decltype((i)) ii = i;  //   int & ii = i;

decltype((变量)) 得到的结果永远是引用。

15.6.1.3 decltype后圆括号中是函数
cpp 复制代码
int testf()
{
	return 1;
}

decltype(testf()) tmpv = 14;		// tmpv 类型是一个函数返回值类型 int, 编译器没有调用该testf()函数,只使用了函数testf()返回值类型
decltype(testf) tmpv2;	// tmpv2 = int(void ) ,有返回值 有函数类型,表示一个可调用对象。
function <decltype(testf)> ftmp = testf;	// 声明了一个function 类型,用来代表一个可调用对象。
cpp 复制代码
	int testf()
	{
		return 1;
	}
	const int&& myfunc()
	{
		return 0;
	}
	void test3()
	{
		decltype(testf()) tmpv = 14;		// tmpv 类型是一个函数返回值类型 int, 编译器没有调用该testf()函数,只使用了函数testf()返回值类型
		decltype(testf) tmpv2;	// tmpv2 = int(void ) ,有返回值 有函数类型,表示一个可调用对象。
		//std::function<decltype(testf)> ftmp ;//= testf;	// 声明了一个function 类型,用来代表一个可调用对象。
				decltype(myfunc()) mm = 1;
    }

const int && myfunc()
{
  return 0;
}

void test3()
{
  int tmpv = 14;
  int tmpv2();
  const int && mm = 1;
}

15.6.2 decltype 主要用途,C++11方式

15.6.1.1 用途1:应付可变类型

问题引入:decltype 应用于模板编程中。

cpp 复制代码
	template <typename T>
	class TMP
	{
	public:
		typename T::iterator iter; // std::vector<int>::iterator iter = 
		// T 用于告诉编译器这是一个类型
		void getbegin(T& tmpc)
		{
			iter = tmpc.begin();
		}
	};

	void test()
	{
		using conttype = std::vector<int>;
		conttype myarr = { 10,20,30 };
		TMP<conttype> ct;
		ct.getbegin(myarr);
	}

编译器推导后:

cpp 复制代码
/* First instantiated from: insights.cpp:22 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
class TMP<std::vector<int, std::allocator<int> > >
{
  
  public: 
  __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > iter;
  inline void getbegin(std::vector<int, std::allocator<int> > & tmpc)
  {
    this->iter.operator=(tmpc.begin());
  }
  
  // inline constexpr TMP() noexcept = default;
};

#endif

void test()
{
  using conttype = std::vector<int>;
  std::vector<int, std::allocator<int> > myarr = std::vector<int, std::allocator<int> >{std::initializer_list<int>{10, 20, 30}, std::allocator<int>()};
  TMP<std::vector<int, std::allocator<int> > > ct = TMP<std::vector<int, std::allocator<int> > >();
  ct.getbegin(myarr);
}

std::allocator 是分配器。

现在将 using conttype = const std::vector; 声明为常量,修改后代码报错,使用C++98方式解决:类模板的偏特化。但是这种处理方式不好,偏特化只是为了解决类型问题,在偏特化出来的类模板中,其他成员函数都要重写一遍。

cpp 复制代码
	template <typename T>
	class TMP
	{
	public:
		typename T::iterator iter; // std::vector<int>::iterator iter = 
		// T 用于告诉编译器这是一个类型
		void getbegin(T& tmpc)
		{
			iter = tmpc.begin();
		}
	};

	// 加上这个偏特化版本后可解决  const std::vector<int>; 问题
	template<typename T>
	class TMP<const T>  // 参数范围上的偏特化
	{
		typename T::iterator iter; // std::vector<int>::iterator iter = 
		// T 用于告诉编译器这是一个类型
		void getbegin(T& tmpc)
		{
			iter = tmpc.begin();
		}
	};
	void test()
	{
		using conttype = const std::vector<int>;   // 加上const 之后报错,因为上面的 T::iterator iter; 而不是 T::const_iterator iter
		//using conttype = std::vector<int>;
		conttype myarr = { 10,20,30 };
		TMP<conttype> ct;
		ct.getbegin(myarr);
	}

实现 decltype 解决:不需要偏特化了,传递什么类型,就能识别什么类型。

cpp 复制代码
	// 使用 decltype 改进
	template <typename T>
	class TMP
	{
	public:
		decltype(T().begin()) iter;
		// T 用于告诉编译器这是一个类型
		void getbegin(T& tmpc)
		{
			iter = tmpc.begin();
		}
	};

	void test()
	{
		using conttype = const std::vector<int>;   // 加上const 之后报错,因为上面的 T::iterator iter; 而不是 T::const_iterator iter
		//using conttype = std::vector<int>;
		conttype myarr = { 10,20,30 };
		TMP<conttype> ct;
		ct.getbegin(myarr);
	}
}
15.6.1.2 decltype 与 临时对象的使用

decltype 并没有真正构造对象,也没有真正调用A的成员函数func,而是直接获取了func的返回值。

cpp 复制代码
	class A
	{
	public:
		A()
		{
			cout << "A()" << endl;
		}
		~A()
		{
			cout << "~A()" << endl;
		}
		int func() const
		{
			cout << "func()" << endl;
			return 0;
		}
	};

	void test()
	{
		//A().func(); // 临时对象:A() func() ~A()
		//(const A()).func(); // 临时对象:A() func() ~A()
		decltype(A().func()) x = 0; // 直接调用返回值。
	}
15.6.1.3 通过变量表达式抽取变量类型

decltype 与 typedef 使用。

cpp 复制代码
	void test()
	{
		vector<int> ac = { 1,2,3 };
		vector<int>::size_type mysize = ac.size();
		cout << mysize << endl;

		decltype(ac)::size_type mysize2 = ac.size();
		cout << mysize2 << endl;
		// 
		typedef decltype(sizeof(double)) mysize3;  // mysize3 = size_t
		mysize3 mysize4 = 12;
	}
15.6.1.4 decltype 与 auto构成后置语法

回顾函数后置返回值。表示函数返回类型放到参数列表之后,通过 -> 开始。

cpp 复制代码
	auto func(int a) -> int
	{
		return a;
	}
	
int &tf(int &i)
{
	return i;
}

double tf(double &d)
{
	return d;
}
// 调用整形
template<typename T>
auto FuncTmp(T &tv)->decltype(tf(tv))   // 返回类型后置 推断的含义
{
	return tf(tv); 
}
void test()
{
	int i3 = 1;
	FuncTmp(i3);
}
	使用 auto 无法完成 decltype功能。因为直接使用auto(a+b)时,a b 还没有被定义。
	auto func2(int a, int b) -> decltype(a + b)
	{
		return a + b;
	}
	//auto (a + b) func3(int a, int b)  // 这种写法不被允许,引用 a+b还没有定义
	//{
	//	return a + b;
	//}
15.6.1.5 decltype(auto) 方式

如何理解 这种方式呢?auto 理解为自动类型推倒,decltype 理解为 推倒过程中采用 decltype 方式不丢失 const 和 & 来推倒。

1 decltype(auto) 在函数中使用
cpp 复制代码
	template <typename T>
	T& mydouble(T& v1)
	{
		v1 = v1 * 2;
		return v1;
	}
	// 使用 auto 修改上面函数的返回值类型
	template <typename T>
	auto mydouble2(T& vl) -> decltype(vl)
	{
		vl = vl * 2;
		return vl;
	}

	template <typename T>
	auto mydouble3(T& vl)  // 直接用auto 返回,会丢弃const 和 &,所以返回的是右值
	{
		vl = vl * 2;
		return vl;
	}

	template <typename T>
	decltype(auto) mydouble4(T& vl) // C++14 引入语法既包含自动推倒,又不丢失 const 和 & 属性
	{
		vl = vl * 2;
		return vl;
	}

	void test3()
	{
		int a = 100;
		mydouble2(a) = 22;
		mydouble3(a) = 22;  // 这里报错:因为上边返回的不是左值,而是右值
	}
2 decltype(auto) 在变量中使用

根据后边的值推倒前边。

cpp 复制代码
	void test()
	{
		int a = 100;
		const int& y = 1;
		auto z = y; // z = int
		decltype(auto) z2 = y; // z2 = const int & 
	}

15.7可调用对象、std::function、std::bind

15.7.1 可调用对象

可调用对象主要有两种,函数指针和重载了() 运算符的类对象。

15.7.1.1 函数指针
cpp 复制代码
static void myfunc(int v)
{}
void(*pf)(int) = myfunc;
pf(15);	// 函数调用
15.7.1.2 具有operator() 成员函数的类对象(仿函数,函数对象)

C++ 仿函数通过类中重载()运算符,又称为函数对象(仿函数 something that performs a function );

调用方式:对象+(),比如 对象() 。

cpp 复制代码
    class TC
    {
    public:
        void operator()(int tv)
        {
            cout << "仿函数" << tv << endl;
        }

        void pffunc(int tv)
        {
            cout << "TC pffunc 执行了" << endl;
        }

        static int stcfunc(int tv)
        {
            cout << "类的静态函数执行" << endl;
            return tv;
        }
    };

    void test()
    {
        TC tc;
        tc(20);	// 调用 operator() , 这也是可调用对象,类似于函数调用。
    }
15.7.1.3 可被转换为函数指针的函数对象

类型转换运算符在类中,只能转换static 类型的函数,因为非static 成员函数,有一个默认的this指针。

可被转换为函数指针的类对象也可以叫作仿函数或函数对象。在MyProject.cpp前面增加如下TC2类定义:

cpp 复制代码
	class TC2
	{
	public:
		using tfpoint = void(*)(int);

		static void mysfunc(int tv)
		{
			cout << "TC2::mysfunc()静态成员函数执行了,tv=" << tv << endl;
		}

		operator tfpoint()
		{
			return mysfunc;
		}
	};

	void test()
	{
		TC2 tc2;
		tc2(100); // 先调用 tfpoint() 转换函数,然后调用 mysfunc() 静态成员函数
	}

类型转换函数的用法:

下面例子,类型转换函数可以将一个类型转为double类型。

cpp 复制代码
	class Fraction
	{
	public:
		// 放置隐士转换
		explicit Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den)
		{
		}

		operator double() const
		{
			return (double)m_numerator / m_denominator;
		}
	private:
		int m_numerator;  // 分子
		int m_denominator; // 分母	
	};

	void test3()
	{
		Fraction f(3, 5);
		double d = f; // 调用 operator double() 函数
		cout << d << endl;
	}
15.7.1.4 类成员函数指针
cpp 复制代码
class TC
{
public:
	void operator()(int tv)
	{
		cout << "仿函数" << tv << endl;
	}

	void pffunc(int tv)
	{
		cout << "TC pffunc 执行了" << endl;
	}

	static int stcfunc(int tv)
	{
		cout << "类的静态函数执行" << endl;
		return tv;
	}
};

    void test()
    {
        TC tc2;
        void (TC:: * myfpoint)(int) = &TC::pffunc;
        (tc2.*myfpoint)(2);  // 也是一个可调用对象
        void (TC:: * myfunc)(int) = &TC::operator();
        (tc2.*myfunc)(2);
    }
15.7.1.5 总结

其实,可调用对象首先被看作一个对象,程序员可以对其使用函数调用运算符"()",那就可以称其为"可调用的"。换句话说,如果对象a是一个可调用对象,那么就可以编写诸如

a(参数1,参数2,)这样的代码。

如果找通用性,这几种可调用对象的调用形式都比较统一,除了类成员指针写法比较特殊以外,其他几种可调用对象的调用形式都是"名字(参数列表)"。但是,它们的定义方法是五花八门,怎样定义的都有。

那么,有没有什么方法能够把这些可调用对象的调用形式统一一下呢?有,那就是使用std::function把这些可调用对象包装起来。

b 我们可以对其使用() 调用运算符,如果a是可调用对象,那么我们就可以编写a代码。

15.7.2 std::function 可调用对象包装器

如何能把不同的可调用对象的形式统一起来,方便调用。(统一处理函数对象)

std::function 可调用对象包装器,是一个类模板 C++11引入 ,用来包装可调用对象,对外调用方式统一。

std::function 类模板特点,能够通过给它指定模板参数,它就能够用统一的方式调用。

15.7.2.1 function绑定普通函数

function<void(int)> 的 <> 中是 返回值类型+函数参数。

cpp 复制代码
static void myfunc(int v)
{}
std::function<void(int)> f1 = myfunc;
f1(100);
15.7.2.2 function绑定类的静态成员函数

类的静态成员函数。

cpp 复制代码
	class TC
	{
	public:
		void operator()(int tv)
		{
			cout << "仿函数" << tv << endl;
		}

		void pffunc(int tv)
		{
			cout << "TC pffunc 执行了" << endl;
		}
		static int stcfunc(int tv)
		{
			cout << "类的静态函数执行" << endl;
			return tv;
		}
	};

	void test()
	{
		std::function<int(int)> f = TC::stcfunc;
		f(2);
	}
15.7.2.3 function绑定仿函数

function 绑定仿函数时只需要将 重载了 operator类的对象赋值给 function.

cpp 复制代码
	TC tc3;
	std::function<void(int)> f3 = tc3;
	f3(2);
15.7.2.4 使用示例
范例1:function作为函数参数

函数对象function<> 作为函数参数传递,并将重载了operator的类对象绑定到 function上。

cpp 复制代码
	class CB
	{
	public:
		// 初始化一个无参函数对象
		CB(const std::function<void()>& f) : fcallback(f)
		{
			int i;
			i = 1;
		}

		void runcallback(void)
		{
			fcallback();
		}
	private:
		std::function<void()> fcallback;   // 可调用对象包装器
	};

	class CT
	{
	public:
		void operator()(void)
		{
			cout << "TT operator() 执行了" << endl;
		}
	};

	void test()
	{
		CT ct;		// 可调用对象
		CB cb(ct);	// cb 需要可调用对象左参数来构造,ct因为有 operator() 所以可以转换 std::function<void()&>对象 ; 
		cb.runcallback();
	}
范例2 :function作为函数参数 实现回调功能
cpp 复制代码
// 定义回调函数
void mycallback(int cs, const std::function<void(int)> &f)
{
    f(cs);
}

// 定义要运行回调函数
void runfunc(int x)
{
    cout << x << endl;
}

int test()
{
    // 循环调用回调函数
    for (int i = 0; i < 10; ++i)
    {
        mycallback(i, runfunc);
    }
}

15.7.3 std::bind 绑定器

C++11 引入的bind绑定器。

std::bind 能够将对象以及相关参数绑定在一起,绑定完之后直接调用,也可以用std::function 进行保存;

cpp 复制代码
格式
	/*
		std::bind(待绑定的函数对象/函数指针/成员函数指针, 参数绑定值1,参数绑定值2,... 参数绑定值n)
		总结:
		1 将可调用对象和参数绑定在一起,构造一个仿函数,所以可以直接调用。
		2 如果函数有多个参数,可以绑定一部分参数,其他参数在调用的时候制定。
	*/

void myfunc1(int x, int y, int z)
{
	cout << x << " " << y << " " << z << "	" << endl;
}

void myfun2(int &a, int &b)
{
	a++;
	b++;
}

    void test()
    {
        auto bf1 = std::bind(myfunc1, 1, 2, 3);	// auto 不关心bind 返回类型,其他bind返回一个仿函数对象,
        bf1();		// 执行仿函数
        auto bf2 = std::bind(myfunc1, placeholders::_1, placeholders::_2, 30);  // 只是占位符,调用bf2时候,再制定第二个参数。
        bf2(1, 2);
    }	
15.7.3.1 bind 占位符

系统定义了20个占位符,足够我们使用了。

cpp 复制代码
auto bf2 = std::bind(myfunc1, placeholders::_1, placeholders::_2, 30);  // 只是占位符,调用bf2时候,再制定第二个参数。
	bf2(1,2);
15.7.3.2 bind 值传递

对于placeholders绑定的参数,是通过值传递的;直接占位是引用传递。

cpp 复制代码
	int a = 2;
	int b = 3;
	auto bf3 = std::bind(myfun2,placeholders::_1,a);
	cout << a << endl;			// 对于绑定的参数,是通过值传递的;
	cout << b << endl;			// 对于bind是通过
15.7.3.3 绑定成员函数

如果绑定成员函数,第一个参数绑定类的成员函数,第二个参数绑定对象的引用;否则会出现调用成员函数。

cpp 复制代码
class CT
{
public:
	void myfuncf(int a, int b)
	{
		ma = a;
	}
	int ma;
	CT()
	{
		cout << "构造函数" << endl;
	}

	CT(const CT & ct)
	{
		cout << "拷贝构造函数" << endl;
	}
	void operator()(void)
	{
		cout << "operator() 执行" << endl;
	}
};
void test()
{
	CT ct;
	// auto bf5 = std::bind(&CT::myfuncf, ct, 	std::placeholders::_1, std::placeholders::_2);
	auto bf5 = std::bind(&CT::myfuncf, &ct, std::placeholders::_1, std::placeholders::_2);
	bf5(10, 20);	// 上行第二个参数ct,会导致调用CT的拷贝构造函数来生成一个CT 临时对象,作为std::bind的返回值(bind);
	// 后续 myfunc 成员函数调用,修改的是临时对象的ma值;并不会影响真实的ct对象的值。
}
15.7.3.4 绑定类的对象
cpp 复制代码
	auto rt = std::bind(CT());
	rt();

15.7.4 function 和 bind 结合使用

示例1: function + bind 结合使用
cpp 复制代码
// 示例函数:普通函数
int add(int a, int b) 
{
    return a + b;
}

// 示例类
class Calculator 
{
public:
    int multiply(int a, int b) 
    {
        return a * b;
    }
};

int main() {
    // 使用 std::function 封装普通函数
    std::function<int(int, int)> addFunction = add;
    std::cout << "addFunction(3, 4): " << addFunction(3, 4) << std::endl;  // addFunction(3, 4): 7

    // 使用 std::bind 绑定参数
    std::function<int(int)> addThree = std::bind(add, 3, std::placeholders::_1);
    std::cout << "addThree(5): " << addThree(5) << std::endl;           // addThree(5): 8

    // 使用 std::function 封装类的成员函数
    Calculator calc;
    std::function<int(Calculator&, int, int)> multiplyFunction = &Calculator::multiply;
    std::cout << "multiplyFunction(calc, 2, 6): " << multiplyFunction(calc, 2, 6) << std::endl;// multiplyFunction(calc, 2, 6): 12

    // 使用 std::bind 绑定类成员函数和对象
    std::function<int(int)> multiplyByTwo = std::bind(&Calculator::multiply, &calc, std::placeholders::_1, 2); 
    std::cout << "multiplyByTwo(7): " << multiplyByTwo(7) << std::endl;// multiplyByTwo(7): 14
    return 0;
}
示例2: 使用function+map实现 switch功能

map<int, function<void()>> actionMap;

cpp 复制代码
void doShowAllBooks() { cout << "查看所有书籍信息" << endl; }
void doBorrow() { cout << "借书" << endl; }
void doBack() { cout << "还书" << endl; }
void doQueryBooks() { cout << "查询书籍" << endl; }
void doLoginOut() { cout << "注销" << endl; }

int main()
{
	int choice = 0;
	map<int, function<void()>> actionMap;
	actionMap.insert({1,doShowAllBooks});
	actionMap.insert({ 2, doBorrow });
	actionMap.insert({ 3, doBack });
	actionMap.insert({ 4, doQueryBooks });
	actionMap.insert({ 5, doLoginOut });

	while (1)
	{
		cout << "-------------------" << endl;
		cout << "1.查看所有书籍信息" << endl;
		cout << "2.借书" << endl;
		cout << "3.还书" << endl;
		cout << "4.查询书籍" << endl;
		cout << "5.注销" << endl;
		cout << "-------------------" << endl;
		cout << "请选择";
		cin >> choice;
		if (choice == 0)
		{
			break;
		}
		auto it = actionMap.find(choice);
		if (it == actionMap.end())
		{
			cout << "输入无效,请重新输入" << endl;
		}
		else
		{
			it->second();
		}
	}

	system("pause");
	return 1;
}

使用for_each 遍历。

cpp 复制代码
		map<int, function<void()>> menu = {
			{1, doShowAllBooks},
			{2, doBorrow},
			{3, doBack},
			{4, doQueryBooks},
			{5, doLoginOut}
		};

		menu.insert(make_pair(2, doBorrow));

		for_each(menu.begin(), menu.end(), [](const pair<int, function<void()>>& pair) {
			pair.second();
			});
示例3: function的实现原理

Function的实现原理就是 operator() 运算符重载。

cpp 复制代码
// 实现function类 
template<typename Tty>
class myfunction
{
	
};
/*
// 实现一个参数的function 类,相当于myfunction的部分特例化
template<typename R, typename A1>  // R返回值类型,A1参数类型
class myfunction<R(A1)>
{
public:
	using PFUNC = R(*)(A1);
	myfunction(PFUNC pfunc) :_pfunc(pfunc) {}

	R operator()(A1 arg)  // arg 实参
	{
		return _pfunc(arg);
	}
private:
	PFUNC _pfunc;
};

template<typename R,typename A1,typename A2>
class myfunction<R(A1, A2)>  // 返回值(参数类型1,参数类型2)
{
public:
	using PFUNC = R(*)(A1, A2);
	myfunction(PFUNC pfunc) : _pfunc(pfunc) {}
	R operator()(A1 arg1, A2 arg2)
	{
		return _pfunc(arg1, arg2);
	}
private:
	PFUNC _pfunc;
};
*/
// 实现多个参数的function
template<typename R,typename...A>
class myfunction<R(A...)>
{
public:
	using PFUNC = R(*)(A...);
	myfunction(PFUNC pfunc) :_pfunc(pfunc)
	{
		
	}

	R operator()(A...arg)
	{
		return _pfunc(arg...);
	}

private:
	PFUNC _pfunc;
};

void hello(string str) { cout << str << endl; }
int sum1(int a, int b) { return a + b; }
int main()
{
	// 使用类库的function 
	function <void(string)> func1(hello);
	func1("hello world!");

	myfunction <void(string)> func3(hello);
	func3("hello world!");

	function<int(int, int)> func2 = sum1;
	cout << func2(1, 2) << endl;

	myfunction<int(int, int)> func4 = sum1;
	func4(1, 2);

	system("pause");
	return 1;
}
示例4: bind和function实现线程池

在线程池中,通过bind 绑定到线程池中的成员函数,然后在线程类中利用function<void(int)> func, 接受bind绑定的参数。

在给 参数为function的函数传递参数时,实参使用 bind传递。

实现一个简单的线程池:

cpp 复制代码
	// 实现线程池 finction 和 bind 
    class CMyThread
    {
    public:
        CMyThread(function<void(int)> func, int number)
            : m_func(func)
            , m_iThreadNumber(number)
        {

        }
        ~CMyThread()
        {

        }

        thread start()
        {
            return thread(m_func, m_iThreadNumber);
        }

    private:
        function<void(int)> m_func;
        int m_iThreadNumber;
    };

    class MyThreadPool
    {
    public:
        MyThreadPool() {}
        ~MyThreadPool()
        {
            // 释放thread对象占用的堆资源 
            for (int i = 0; i < m_vecThreadPool.size(); ++i)
            {
                delete m_vecThreadPool[i];
            }
        }

        static void func(int i)
        {
            cout << "这是第 " << i << "个线程" << endl;
            std::this_thread::sleep_for(std::chrono::seconds(i));
        }

        // 开启线程池 
        void startpool(int size)   // 线程池数量size 
        {
            // 创建 size 个线程 
            for (int i = 0; i < size; ++i)
            {
                CMyThread* pThread = new CMyThread(func, i);
                // 绑定类的成员函数 必须使用bind绑定
                //CMyThread* pThread = new CMyThread(bind(&MyThreadPool::threadfunc, this, std::placeholders::_1), i);
                m_vecThreadPool.emplace_back(pThread);
                m_threadHandler.emplace_back(pThread->start());
                m_threadHandler[i].join();
            }
        }

        // 线程执行函数
        void threadfunc(int i)
        {
            cout << "这是第 " << i << "个线程" << endl;
            std::this_thread::sleep_for(std::chrono::seconds(i));
        }
        // void
    private:
        vector<CMyThread*> m_vecThreadPool;
        vector<thread>     m_threadHandler;
    };

    void test()
    {
        MyThreadPool mypool;
        mypool.startpool(5);
        /*
            这是第 0个线程
            这是第 1个线程
            这是第 2个线程
            这是第 3个线程
            这是第 4个线程
        */
    }

15.7.5 总结

bind思想,延迟调用,将可调用对象统一格式,保存起来,需要的时候再调用。

function提供了统一的函数管理方式。

相关推荐
万象.3 小时前
QT基础及对象树的认识
c++·qt
小张成长计划..5 小时前
【C++】2:cin和cout的介绍和使用,函数的缺省参数
c++
再卷也是菜5 小时前
C++篇(17)哈希拓展学习
c++·哈希
“愿你如星辰如月”5 小时前
Linux:进程间通信
linux·运维·服务器·c++·操作系统
灵晔君6 小时前
C++标准模板库(STL)——list的模拟实现
c++·list
Justinyh7 小时前
1、CUDA 编程基础
c++·人工智能
white-persist8 小时前
差异功能定位解析:C语言与C++(区别在哪里?)
java·c语言·开发语言·网络·c++·安全·信息可视化
ShineWinsu8 小时前
对于数据结构:链式二叉树的超详细保姆级解析—中
数据结构·c++·算法·面试·二叉树·校招·递归
liu****9 小时前
20.传输层协议TCP
服务器·网络·数据结构·c++·网络协议·tcp/ip·udp