《C++新经典》第20章 高级话题与新标准

130 篇文章 4 订阅
92 篇文章 19 订阅

《C++新经典》第20章 高级话题与新标准

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

20.1.2 函数调用运算符

若类重载了函数调用运算符(),就可以像使用函数一样使用该类的对象(像函数调用一样来调用该类的对象)。

class biggerthanzero {
public:
	int operator()(int value) const {
		if(value<0)
			return 0;
		return value;
	}
};

int i = 200;
biggerthanzero obj;
int result = obj(i); //等价于int result = obj.operator()(i);
class biggerthanzero {
public:
	biggerthanzero(int i) {
		cout<<"biggerthanzero::biggerthanzero(int)\n";
	}
	int operator()(int value) const {
		if(value<0)
			return 0;
		return value;
	}
};

int i = 200;
biggerthanzero obj(i);//对象定义并初始化,调用的是biggerthanzero构造函数
int result = obj(i); //等价于int result = obj.operator()(i);

若类重载了()运算符(可有多个版本,参数类型或数量有差别即可),则类对象就变成了可调用对象(函数对象)。

20.1.3 不同调用对象的相同调用形式

int echovalue(int value) {
	cout<<value<<endl;
	return value;
}

echovalue函数和biggerthanzero类重载的函数调用符具有相同的形参和返回值,叫做调用形式相同。

一种调用形式对应一个函数类型。
int(int)表示接收一个int参数,返回一个int值的函数类型。

可以将可调用对象(函数对象、仿函数)的指针保存起来,方便后续随时调用。

map<string, int(*)(int)> myoper;
myoper.insert({"ev", echovalue});

//系统不会将类biggerthanzero或类对象obj看成函数指针
biggerthanzero obj;
myoper.insert({"bt", biggerthanzero});//error
myoper.insert({"bt", obj});	//error

20.1.4 标准库function类型简介

function,类模板,用来包装可调用对象,统一了类型。

biggerthanzero obj;
function<int(int)> f1 = echovalue;
function<int(int)> f2 = obj;
function<int(int)> f3 = biggerthanzero();

f1(5);
f2(3);
f3(-5);
biggerthanzero obj;
map<string, function<int(int)>> myoper = {
	{"ev", echovalue},
	{"bt", obj}
};
myoper.insert({"bt2", biggerthanzero()});

myoper["ev"](12);
myoper["bt"](3);
myoper["bt2"](-5);

只要函数是重载的,就无法包装进function中。
可以通过定义函数指针解决。

int echovalue(int value) {
	cout<<value<<endl;
	return value;
}

void echovalue() {
	return;
}

function<int(int)> f1 = echovalue; //error

int(*fp)(int) = echovalue;
function<int(int)> f1 = fp; //ok

20.2 万能引用

20.2.1 类型区别基本概念

void func(const int&abc){}
//abc类型为const int&
template<typename T>
void func(const T&abc){}

func(10);
//T(类型模板参数)的类型是int(与输入参数10和abc类型即const int &有关)
//abc(变量)的类型是const int &

20.2.2 universal reference基本认识

universal reference(forwarding reference,转发引用),万能引用(未定义引用)。

万能引用是一种类型。

void func(int &&tmp) {//tmp为右值引用类型
	cout<<tmp<<endl;	
}

myfunc(10);//右值作为实参
int i = 100;

//右值引用不能接左值(形参为右值引用类型,实参不能是左值)
myfunc(i); //error
//函数模板推断+函数形参为T&&会出现万能引用
//auto && tmp = ...也是万能引用
//实参传递左值,则万能引用是左值引用(tmp被推断为int &类型,假设传递int)
//实参传递右值,则万能引用是右值引用(tmp被推断为int &&类型,假设传递int)
//T&&(tmp)是万能引用,T不是
template<typename T>
void func(T&& tmp){//tmp类型为T&&,&&和T类型无关
	cout<<tmp<<endl;
}
void func(int &&param){}//右值引用

template<typename T>
void func(T&& tmp){} //万能引用


template<typename T>
void func(vector<T>&& tmp){} //右值引用
template<typename T>
void myfunc(T&& tmp){
	tmp = 12;
	cout<<tmp<<endl;
}

int i = 100;
myfunc(i);//左值引用
i = 200;
myfunc(std::move(i)); //右值引用

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

  1. 剥夺
//const剥夺万能引用资格,const T&&只能是右值引用。
template<typename T>
void myfunc(const T&& tmp){//右值引用
	tmp = 12;
	cout<<tmp<<endl;
}

int i = 100;
myfunc(i); //error
myfunc(std::move(i));//ok 
  1. 辨认
template<typename T>
class mytestc {
public:
	void testfunc(T&& x){}//右值引用,不是万能引用
};

mytestc<int> mc;
int i = 100;
mc.testfunc(i); //error
mc.testfunc(std::move(i)); //ok
template<typename T>
class mytestc {
public:
	void testfunc(T&& x){}//右值引用,不是万能引用
	template<typename T2>
	void testfunc2(T2&& x){}//万能引用
};

mytestc<int> mc;
int i = 100;
mc.testfunc2(i); //ok
mc.testfunc2(std::move(i)); //ok

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

20.3.1 如何查看类型推断结果

boost

#include <boost/type_index.hpp>

template <typename T>
void myfunc(T& tmprv){
	using boost::typeindex::type_id_with_cvr;
	cout<<"T = "<<type_id_with_cvr<T>().pretty_name()<<endl;//显示T类型
	cout<<"tmprv = "<<type_id_with_cvr<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}

20.3.2 理解函数模板类型推断

#include <boost/type_index.hpp>

template <typename T>
void myfunc(const T& tmprv){
	using boost::typeindex::type_id_with_cvr;
	cout<<"T = "<<type_id_with_cvr<T>().pretty_name()<<endl;//显示T类型
	cout<<"tmprv = "<<type_id_with_cvr<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}

myfunc(10);
//T是类型模板参数,tmprv是函数模板myfunc的形参。
//T = int
//tmprv = int const &
  1. 指针或引用类型
#include <boost/type_index.hpp>

template <typename T>
void myfunc(T& tmprv){
	using boost::typeindex::type_id_with_cvr;
	cout<<"T = "<<type_id_with_cvr<T>().pretty_name()<<endl;//显示T类型
	cout<<"tmprv = "<<type_id_with_cvr<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}

int i = 18;
myfunc(i);
//T = int
//tmprv = int &

const int j = i;
myfunc(j);
//T = int const
//tmprv = int const &
//myfunc内tmprv不能赋值,即tmprv = 15会报错

const int& k = i;
myfunc(k);
//T = int const
//tmprv = int const &
//myfunc内tmprv不能赋值,即tmprv = 15会报错
#include <boost/type_index.hpp>

template <typename T>
void myfunc(const T& tmprv){
	using boost::typeindex::type_id_with_cvr;
	cout<<"T = "<<type_id_with_cvr<T>().pretty_name()<<endl;//显示T类型
	cout<<"tmprv = "<<type_id_with_cvr<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}

int i = 18;
myfunc(i);
//T = int
//tmprv = int const &
//myfunc内tmprv不能赋值,即tmprv = 15会报错

const int j = i;
myfunc(j);
//T = int
//tmprv = int const &
//myfunc内tmprv不能赋值,即tmprv = 15会报错

const int& k = i;
myfunc(k);
//T = int
//tmprv = int const &
//myfunc内tmprv不能赋值,即tmprv = 15会报错
#include <boost/type_index.hpp>

template <typename T>
void myfunc(T* tmprv){
	using boost::typeindex::type_id_with_cvr;
	cout<<"T = "<<type_id_with_cvr<T>().pretty_name()<<endl;//显示T类型
	cout<<"tmprv = "<<type_id_with_cvr<decltype(tmprv)>().pretty_name()<<endl;//显示tmprv类型
}

int i = 18;
myfunc(&i);
//T = int
//tmprv = int *

const int *pi = &i;
myfunc(pi);
//T = int const
//tmprv = int const *
  1. 万能引用类型
template <typename T>
void myfunc(T&& tmprv){...}

int i = 18;
myfunc(i);
//T = int &
//tmprv = int &

const int j = i;
myfunc(j);
//T = int const &
//tmprv = int const &

const int& k = i;
myfunc(k);
//T = int const &
//tmprv = int const &

myfunc(100);
//T = int
//tmprv = int &&
  1. 传值方式
template <typename T>
void myfunc(T tmprv){...}

int i = 18;
myfunc(i);
//T = int
//tmprv = int

const int j = i;
myfunc(j);
//T = int
//tmprv = int

const int& k = i;
myfunc(k);
//T = int
//tmprv = int

char mystr[] = "test";
const char * const point = mystr;
myfunc(point);
//myfunc(mystr);//同上
//T = char const *
//tmprv = char const *
//tmprv = nullptr;//ok
//*tmprv = 'Y';//error
  1. 数组作为实参
template <typename T>
void myfunc(T tmprv){...}

const char mystr[] = "test";
myfunc(mystr);
//T = char const *
//tmprv = char const *
template <typename T>
void myfunc(T& tmprv){...}

const char mystr[] = "test";
myfunc(mystr);
//T = char const [5]
//tmprv = char const (&)[5]
template <typename T, unsigned L1>
void myfunc(T (&tmprv)[L1]){...}

const char mystr[] = "test";
myfunc(mystr);
//T = char const [5]
//tmprv = char const (&)[5]
  1. 函数名作为实参
template <typename T>
void myfunc(T tmprv){...}

void testFunc(){}

myfunc(testFunc);
//T = void(__cdecl *)(void)
//tmprv = void(__cdecl *)(void)
template <typename T>
void myfunc(T& tmprv){...}

void testFunc(){}

myfunc(testFunc);
//T = void __cdecl (void)
//tmprv = void(__cdecl &)(void)

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

20.4.1 引用折叠规则

template<typename T>
void myfunc(T&& tmprv){...}

int i = 18;
myfunc(i);
//T = int &
//tmprv = int &

myfunc(100);
//T = int
//tmprv = int &&
//第一组&是左值引用,第二组&&是右值引用
//左值相遇、左右值相遇、右值相遇、右左值相遇,会发生&符合的合并,折叠。
void myfunc(int& && tmprv){...}

int i = 18;
myfunc(i);
//tmprv = int &
//第一组&是左值引用,第二组&&是右值引用
//左值相遇、左右值相遇、右值相遇、右左值相遇,会发生&符合的合并,折叠。
void myfunc(int& && tmprv){...}

//编译器内部进行折叠(合并),程序不能写三个&&&
//int b = 500;
int& byi = b;
//int& &byy = byi;//error
//int& &byy2 = b;//error

左值-左值,&-&
左值-右值,&-&&
右值-左值,&&-&
右值-右值,&&-&&
存在左值引用,结果就是左值引用。上述组合结果为:
左值引用,&
左值引用,&
左值引用,&
右值引用,&&

20.4.2 转发与完美转发

转发,把收到的参数以及这些参数相对应的类型(const、左值、右值等)不变地转发给其他函数的函数模板。

万能引用T&&函数模板的形参,可以保存实参的所有类型信息(const、引用等),编译器据此推断出函数模板最终的形参类型。

template<typename F, typename T1, typename T2>
void myFuncTemp(F f, T1 &&t1, T2 &&t2){...}
//实参中来的左值或者右值信息、const信息都保存在t1和t2参数中
void myfunc(int v1, int& v2){
	++v2;
	cout<<v1+v2<<endl;
}

void myfunc2(int&& v1, int& v2){
	cout<<v1+v2<<endl;
}

//youzhi是一个左值(变量永远是左值,有地址),但是它的类型是右值引用(&&youzhi)。
int&& youzhi = 80;
//形参总是左值,即使其类型是右值引用
void ft(int &&ww){}


int j = 2;
//20右值,j左值
myFuncTemp(myfunc2, 20, j);

template<typename F, typename T1, typename T2>
void myFuncTemp(F f, T1 &&t1, T2 &&t2){
	f(t1, t2);//t1,t2都是左值(变量,形参本身是左值),即使t1是右值引用
}

//v1中v1需要右值,myfunc2传递左值会报错
void myfunc2(int&& v1, int& v2){
	cout<<v1+v2<<endl;
}

20.4.3 std::forward

专门为转发而存在的函数,要么返回左值,要么返回右值。
实参左值,std::forward后还是左值;
实参右值,形参变为左值,std::forward后转为右值。
std::forward能够保持原始实参的左值或者右值性。

void printInfo(int& t){
	cout<<"int&"<<endl;
}
void printInfo(int&& t){
	cout<<"int&&"<<endl;
}

template<typename T>
void testF(T&& t){
	using boost::typeindex::type_id_with_cvr;
	cout<<"T = "<<type_id_with_cvr<T>().pretty_name()<<endl;
	cout<<"t = "<<type_id_with_cvr<decltype(t)>().pretty_name()<<endl;

	printInfo(t);
	printInfo(std::forward<T>(t));
	printInfo(std::move(t));
}

TestF(1);
/*
T = int
t = int &&
int&
int&&
int&&
*/

int i = 1;
TestF(i);
/*
T = int &
t = int &
int&
int&
int&&
*/
int ix = 12;
//std::forward<int>(ix)转为右值
//std::forward<int &>(ix)转为左值
int&& def = std::forward<int>(ix);

完美转发,将任意的函数名、任意类型参数(个数确定)(保持参数类型不变)传递给函数模板,达到间接调用函数的目的。

20.4.4 std::move与std::forward的区别

  • std::move无条件强制转换为右值(原来左值,强制转成右值;原来右值,保持不变);std::forward某种条件下执行强制转换(原来左值,保持不变,原来右值,因为参数传递等变成左值,std::forward能转换回右值);
  • std::move只需要普通参数,std::forward需要模板类型参数和普通参数;
  • std::move左值后,不应该使用这个左值了;std::forward转发对象,保持对象的左值性或右值性。

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

20.5.1 auto类型常规推断

声明变量时根据变量初始值的类型自动推断匹配类型。
auto特点:

  • auto自动类型推断发生在编译期间;
  • audo定义变量必须立即初始化,才能推断auto类型和整个变量类型;
  • auto可以和指针、引用、const等结合使用。
//auto理解为类型模板参数T,x理解为模板中形参类型。
//T(auto) = int
//tmprv(x) = int
auto x = 27;
  1. 传值方式(非指针,非引用)
    auto后面直接跟变量名。
auto x = 27;//T = int, t = int
const auto x2 = x;//T = int, t = const int
const auto& xy = x;//T = int, t = const int&
auto xy2 = xy;//T = int, t = int

传值方式针对auto类型会抛弃引用、const等限定符。

  1. 指针或引用类型但不是万能引用
    auto后面接一个&,不会抛弃const限定符,但是会抛弃引用。
auto x = 27;
const auto& xy = x;//const int&

auto& xy3 = xy;//const int &, auto = const int
auto y = new auto(100); // y = int*, auto = int *,auto可用于new操作符

const auto* xp = &x;//const int *, auto = int
auto * xp2 = &x; //int *, auto = int
auto xp3 = &x;// int *,
  1. 万能引用类型
    auto后面接&&。
auto x = 22;
auto&& wnyy0 = 222;//int&&, auto = int
auto&& wnyy1 = x;//int&, auto = int&

const auto x2 = x;//const int
auto&& wnyy3 = x2;//int const &, auto = int const &

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

const char mystr[] = "test";//const char[5]
auto myarr = mystr;//char const *
auto& myarr2 = mystr;//char const(&)[5]

int a[2] = {1, 2};
auto aauto = a;// int *
void myfunc3(double, int){
	cout<<"myfunc3"<<endl;
}

auto tmpf = myfunc3;//void(__cdecl *)(double, int),函数指针
tmpf(1, 2);

auto& tmpf2 = myfunc3;//void(__cdecl &)(double, int),函数引用
tmp2(1, 2);

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

初始化变量方法:

int x = 10;//c++98
int x2(20);//c++98
int x3 = {30};//c++11
int x4{40};//c++11
auto x = 10;//int
auto x2(20);//c++98
auto x3 = {30};//std::initializer_list<int>,隐式类型转换
auto x4{40};//int

std::initializer_list是c++1中类模板,数组,与vector类型。

auto x5 = {30, 21};
auto x6 = {30, 21, 45.3};//error,类型不一致
template<typename T>
void fautof(T param){}

fautof({12});//error
template<typename T>
void fautof(std::initializer_list<T> param){}

fautof({12});//ok
//c++14支持
auto funca(){
	return 12;
}

20.5.4 auto不适合场合

(1)不能用于函数参数。

void myfunc(auto x, int y){}//error

(2)不能类中普通成员变量。

class CT{
public:
	//auto m_i = 12;//error
	static const auto m_si = 15;//ok
};

20.5.5 auto适合场合

std::map<string, int> mymap;
mymap.insert({"aa", 1});
mymap.insert({"bb", 2});
mymap.insert({"cc", 3});
mymap.insert({"dd", 4});

//for(std::map<string, int>::iterator iter = mymap.begin(); iter != mymap.end(); ++iter)
for(auto iter = mymap.begin(); iter != mymap.end(); ++iter)
	cout<<iter->first<<" = "<<iter->second<<endl;
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;
}

cout<<ftestclass<A>()<<endl;
cout<<ftestclass<B>()<<endl;

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

20.6.1 decltype含义和举例

decltype用于推导表达式或者变量名的类型,与auto类型。
decltype特点:

  • decltype的自动类型推断发生在编译器,与auto一样;
  • decltype不会真正计算表达式的值;
  • const限定符、引用属性等可能会被auto抛弃,decltype一般不会抛弃任何东西。
  1. decltype后的圆括号中是变量
const int i = 0;
const int& iy = i;
auto j1 = i;//传值方式推断,引用、const等被抛弃,int
decltype(i) j2 = 15;//const int
decltype(iy) j3 = j2;//const int &
class CT{
public:
	int i;
	int j;
};

decltype(CT::i) a;//int
CT tmpct;
decltype(tmpct) tmpct2;//CT
decltype(tmpct2.i) mv = 5;//int
int x = 1;
auto&& z = x;//万能引用,x左值,auto是int&,z也是int&
int y = 2;
decltype(z) &&h = y;//int & &&,折叠引用,最终int &h = y;
  1. decltype后的圆括号中是表达式
decltype(8) kkk = 5;//int

int i = 0;
decltype(i) k2;//int

int *pi = &i;
decltype(pi) k;//int *
*pi = 4;
//*pi是左值,*pi是表达式不是变量
//如果表达的结果能够作为赋值语句等号左侧的值(*pi=4;)
//那么decltype后返回的就是一个引用
decltype(*pi) k3 = i;//int &
//(i)表达式,i左值
//decltype((变量))的结果永远是引用
decltype((i)) iy3 = i;//int &

int& iy = i;
decltype(iy+1) j;//int
  1. decltype后的圆括号中是函数
int testf(){
	return 10;
}

decltype(testf()) tmpv = 14;//int
decltype(testf) tmpv2;//int(void),可调用对象

function<decltype(testf)> ftmp = testf;
cout<<ftmp()<<endl;

decltype(testf) * tmpv3;//int(*)(void)
tmpv3 = testf;
cout<<tmpv3()<<endl;


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

decltype(myfunctest()) myy = 0;//const int &&

20.6.2 decltype主要用途

  1. 应付可变类型
template<typename T>
class CTTMP{
public:
	typename T::iterator iter;
	void getbegin(T &tmpc){
		iter = tmpc.begin();
	}
};

using conttype = std::vector<int>;
conttype myarr = {10, 30, 40};
CTTMP<conttype> ct;
ct.getbegin(myarr);
//using conttype = const std::vector<int>;//error,
常量容器定义初始化,begin等返回常量迭代器vector<int>::const_iterator,而不是vector<int>::iterator
c++98通过写类模板偏特化解决
template<typename T>
class CTTMP<const T>{
public:
	typename T::const_iterator iter;
	void getbegin(const T &tmpc){
		iter = tmpc.begin();
	}
};
template<typename T>
class CTTMP{
public:
	decltype(T().begin()) iter;
	void getbegin(T &tmpc){
		iter = tmpc.begin();
	}
};
class A{
public:
	A(){
		cout<<"A()"<<endl;
	}
	~A(){
		cout<<"~A()"<<endl;
	}
	int func() const{
		cout<<"func()"<<endl;
		return 0;
	}
};

A().func();//临时对象,构造函数,func函数,析构函数
(const A()).func();//同上

decltype(A().func()) aaa;//int,无临时对象生成
  1. 通过变量表达式抽取变量类型
vector<int> ac;
ac.push_back(1);
ac.push_back(2);

vector<int>::size_type mysize = ac.size();
cout<<mysize<<endl;

decltype(ac)::size_type mysize2 = mysize;
cout<<mysize2<<endl;

typedef decltype(sizeof(double)) mysize_t;
mysize_t abc;//unsigned int
  1. auto结合decltype构成返回类型后置语法
auto func(int a, int b) -> int{}
auto add(int i, int k) -> decltype(i+k) {
	return i + k ;
}
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);
}

int i = 19;
cout<<FuncTmp(i)<<endl; //int &tf(int& i)
double d = 28.1;
cout<<FuncTmp(d)<<endl;//double tf(double& d)

//error,tv未定义
//decltype(tf(tv)) FuncTmp(T& tv){...}
  1. decltype(auto)用法
    c++14使用。

(1)函数返回类型

template<typename T>
T& mydouble(T& v){
	v *= 2;
	return v;
}
int a = 100;
mydouble(a) = 20;
cout<<a<<endl;
template<typename T>
auto mydouble(T& v){//auto推导去掉引用
	v *= 2;
	return v;
}
template<typename T>
decltype(auto) mydouble(T& v){//保留引用
	v *= 2;
	return v;
}

int a = 100;
decltype(mydouble(a)) b = a;//int &

(2)变量声明

int x = 1;
const int& y = 1;
auto z = y;//int
decltype(auto) z2 = y;//z2与y完全一致, const int&

(3)(变量)

int i = 10;
decltype((i)) iy3 = i;//int &
decltype(auto) tf1(){
	int i = 1;
	return i;//int
}
decltype(auto) tf2(){
	int i = 1;
	return(i);//int&
}

decltype(tf1()) testa = 4;//int
int a = 1;
decltype(tf2()) testb = a;//int&
tf2() = 12;//引用值已释放

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

20.7.1 可调用对象

  1. 函数指针
void myfunc(int tv){
	cout<<tv<<endl;
}
void(*pf)(int) = myfunc;//&myfunc也一样
pf(15);
  1. 具有operator()成员函数的类对象(仿函数/函数对象)
    仿函数(functors)/函数对象(function objects):能行使函数功能的类所定义的对象。
class TC{
public:
	void operator()(int tv){
		cout<<tv<<endl;
	}
};
TC tc;
tc(20);//tc.operator()(20);
  1. 可被转换为函数指针的类对象
    可被转换为函数指针的类对象也可以叫做仿函数/函数对象。
class TC2{
public:
	using tfpoint = void(*)(int);
	static void mysfunc(int tv){
		cout<<tv<<endl;
	}
	operator tfpoint(){return mysfunc;}//类型转换运算符/函数
};

TC2 tc2;
//先调用tfpoint,再调用mysfunc,这就是一个可调用对象,等价于tc2.operator TC2::tfpoint()(20);
tc2(20); 
  1. 类成员函数指针
class TC{
public:
	void ptfunc(int tv){
		cout<<tv<<endl;
	}
};
TC tc;
void(TC:: *mfp)(int) = &TC::ptfunc;//类成员函数指针变量mfp定义并赋初值
(tc. *mfp)(20); //调用成员函数,用到tc对象
  1. 总结

可调用(可使用函数运算符“()”)对象,可以类似a(参数1,参数2,…)使用。

可调用对象调用形式统一(名字(参数列表)),定义方法不同。

std::function将可调用对象包装起来,统一可调用对象的调用形式。

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

  1. 绑定普通函数
void func(int v){cout<<v<<endl;}
std::function<void(int)> f = func;
func(10);
  1. 绑定类的静态成员函数
class TC{
public:
	static void func(int v){cout<<v<<endl;}
};

std::function<void(int)> f = TC::func;
f(10);
  1. 绑定仿函数
class TC{
public:
	void operator()(int v){cout<<v<<endl;}
};

TC tc;
std::function<void(int)> f = tc;
f(10);
  1. 范例
void mycallback(int cs, const std::function<void(int)> &f){
	f(cs);
}

void runfunc(int x){
	cout<<x<<endl;
}

for(int i=0; i<10; i++)
	mycallback(i, runfunc);
#include <iostream>
#include <functional>
using namespace std;

class CB
{
    std::function<void()> fcallback;

public:
    ~CB()
    {
        cout << "CB:~CB()" << endl;
    }
    CB(const std::function<void()> &f) : fcallback(f)
    {
        cout << "CB:CB(const std::function<void()> &)" << endl;
    }
    void runcallback()
    {
        fcallback();
    }
};

class CT
{
public:
    ~CT()
    {
        cout << "CT:~CT()" << endl;
    }
    CT()
    {
        cout << "CT:CT()" << endl;
    }
    CT(const CT &)
    {
        cout << "CT::(const CT &)" << endl;
    }
    void operator()()
    {
        cout << "CT::operator()()" << endl;
    }
};

int main()
{
    if (0)
    {
        CT ct;
        const std::function<void()> &f = ct; //CT::(const CT &)
        std::function<void()> fcallback = f ; //CT::(const CT &)
    }

    if (1)
    {
        CT ct;
        CB cb(ct);

        cb.runcallback();
    }

    cout << "Over!" << endl;
    return 0;
}

20.7.3 std::bind绑定器

std::bind是函数模板,取代c++98中的bind1st和bind2nd。
std::bind将对象及相关参数绑定在一起,绑定完后可以直接使用,也可以用std::function保存,需要时调用。

std::bind(待绑定的函数对象/函数指针/成员函数指针,参数绑定值1,参数绑定值2...,参数绑定值n)

std::bind两层意思:

  • 将可调用对象和参数绑定一起,构成仿函数,可以直接调用;
  • 绑定部分参数,其他参数调用时指定。
void myfunc1(int x, int y, int z){
	cout<<"x="<<x<<",y="<<y<<",z="<<z<<endl;
}

auto bf1 = std::bind(myfunc1, 10, 20, 30);
bf1();

auto bf2 = std::bind(myfunc1, placeholders::_1, placeholders::_2, 30);
bf2(5, 15);
std::bind(myfunc1, placeholders::_1, placeholders::_2, 30)(5, 15);

auto bf3 = std::bind(myfunc1, placeholders::_2, placeholders::_1, 30);
bf3(15, 5);
void myfunc2(int& x, int& y){
	x++;
	y += 2;
}

int a = 2;
int b = 3;
//bind预先绑定的参数通过值传递,a值传递
//不事先绑定的参数,placeholders通过引用传递,b引用传递
auto bf4 = std::bind(myfunc2, a, placeholders::_1);
bf4(b);//
cout<<a<<endl;//2
cout<<b<<endl;//5
class CQ{
public:
	void myfunc(int x, int y){
		m_a = x;
	}
	int m_a = 0;
};

CQ cq;
//cq会生成临时CQ对象
//&cq不会生成临时CQ对象
auto bf5 = std::bind(&CQ::myfunc, cq, placeholders::_1, placeholders::_2);
bf5(10, 20);

std::function<void(int, int)> bf6 = std::bind(&CQ::myfunc, &cq, placeholders::_1, placeholders::_2);
bf6(10, 20);
class CQ{
public:
	CQ(){
		cout<<"CQ(): "<<this<<endl;
	}
	CQ(const CQ&){
		cout<<"CQ(const CQ&): "<<this<<endl;
	}
	~CQ(){
		cout<<"~CQ(): "<<this<<endl;
	}
	int m_a = 0;
};

CQ cq;
//成员变量地址当函数一样使用
std::function<int& (void)> bf7 = std::bind(&CQ::m_a, &cq);
bf7() = 7;//cq.m_a = 7


//std::function<int& (void)> bf7 = std::bind(&CQ::m_a, cq);
//会调用两次拷贝构造函数,两次析构函数
//一次cq生成临时对象
//一次bind要返回包装后的CQ对象


//会调用一次构造函数,一次拷贝构造函数,两次析构函数
auto rt = std::bind(CT());
rt();

void mycallback(int cs, const std::function<void(int)> &f){
	f(cs);
}

void runfunc(int x){
	cout<<x<<endl;
}

auto bf = std::bind(runfunc, placeholders::_1);
for(int i=0; i<10; i++)
	mycallback(i, bf);

20.8 lambda表达式与for_each、find_if简介

20.8.1 用法简介

lambda表达式也是一种可调用对象,定义一个匿名函数,并且可以捕获一定范围内的变量。

//lambda表达式一般形式:
//[捕获列表](参数列表) -> 返回类型{函数体;};
auto f = [](int a) -> int {
	return a + 1;
};
cout <<f(1) <<endl;

lambda表达式特点:

  • 是匿名函数,可调用代码单元或者未命名的内联函数;
  • 有返回类型、参数列表、函数体;
  • 可在函数内部定义。

注意:
(1)返回类型后置或者省略(return语句推断返回值类型)。
(2)参数列表可以有默认值。

auto f = [](int a = 8) -> int {
	return a + 1;
};

(3)无参时,参数列表甚至’()'都可以省略。

auto f1 = [](){
	return 1;
};
auto f2 = []{
	return 1;
};

(4)捕获列表[]和函数体{}不能省略。

20.8.2 捕获列表

捕获列表捕获一定范围内的变量。
(1)[]:不捕获任何变量。

int i = 9;
auto f = []{
	return i;//error,无法捕获外部变量i
};
static int i = 9;
auto f = []{
	return i;//ok,可以直接使用局部静态变量
};

(2)[&]:捕获外部作用域中所有变量,作为引用在函数体中使用。

int i = 9;
auto f = [&]{
	i = 5; //&,会修改i值
	return i;
};

(3)[=]:捕获外部作用域中所有变量,作为副本(按值)在函数体中使用,可以使用,不能赋值(常量引用?)。

int i = 9;
auto f = [=]{
	//i = 5; //error,不能赋值
	return i;
};

(4)[this]:用于类中,捕获当前类this指针,让lanbda表达式拥有和当前类成员函数同样的访问权限。使用"&“和”="时,默认添加了此项“this”。
[this]和[=]可以读取,不能修改;[&]可以修改。

class CT{
public:
	int m_i = 5;
	void myfuncpt(int x, int y){
		auto mylambda = [this] {//this,&,=都可以读取成员变量值
			return m_i;
		};
		cout<<mylamba()<<endl;
	}
};

CT ct;
ct.myfuncpt(3, 4);

(5)按值捕获和按引用捕获。

  • [变量名]:按值捕获(不能修改)变量,不捕获其他变量。
  • [&变量名]:按值捕获(能修改)变量,不捕获其他变量。
  • 多个变量名可以逗号分隔。
class CT{
public:
	int m_i = 5;
	void myfuncpt(int x, int y){
		auto mylambda1 = [this] {return m_i;};//不能使用x,y
		auto mylambda2 = [=] {return m_i;};//能使用x,y,不能修改
		auto mylambda3 = [&] {return m_i;};//能使用x,y,能修改
		auto mylambda4 = [this, x, y] {return m_i;};//能使用x,y,不能修改
		auto mylambda5 = [&x, &y] {return x;};//能使用x,y,能修改
	}
};

(6)[=, &变量名]:默认按值捕获所有外部变量,其他变量按引用捕获,多个变量逗号隔开。

auto mylambda = [this, &x, y] {return m_i;};
//或者
auto mylambda = [=, &x] {return m_i;};

(7)[&, 变量名]:默认按引用捕获所有外部变量,其他变量按值捕获,多个变量逗号隔开。

auto mylambda = [&, x] {...};

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

int x = 5;
auto f = [=] {return x;};//5
x = 10;
cout<<f()<<endl;//5

lambda捕获的时刻,已经复制x的值了。
lambda捕获时,已经将所有外部变量值复制一份存储在了lambda表达式变量中。
&引用可以及时访问外部值。

20.8.4 lambda表达式中的mutable

int x = 5;
auto f = [=]() mutable{
	x = 6; //无mutable时x不能修改
	return x;
}
x = 10;
cout<<f()<<endl;//6
//存在mutable时,即使无参数,()也不能省略
auto f = [=]() mutable{
	x = 6; //无mutable时x不能修改
	return x;
}

20.8.5 lambda表达式的类型和存储

lambda表达式的类型称为闭包类型(CLosure Type),闭包:函数内的函数(可调用对象)。
捕获到的外部变量可看作闭包类型的成员变量,这些成员变量在lambda对象创建时被初始化。

auto aa = [](){};
auto bb = [](){};
using boost::typeindex::type_id_with_cvr;
cout<<"aa="<<type_id_with_cvr<<<decltype(aa)>().pretty_name()<<endl;
cout<<"bb="<<type_id_with_cvr<<<decltype(bb)>().pretty_name()<<endl;
//aa和bb是两个类对象,分属于不同的类。

auto f = []{};//f是一个匿名的类类型对象
std::function<int(int)> fc1 = [](int tv){return tv};
fc1(15);//15

//bind中,第一个参数是函数指针
//第二个参数是tv
std::function<int(int)> fc2 = std::bind([](int tv){return tv}, 16);
//bind绑定死tv为16,这里参数15不起作用,除非bind中使用placeholders::_1
//std::bind([](int tv){return tv}, placeholders::_1);
fc2(15);//16

不捕获任何变量的lambda表达式,可转换为普通的函数指针。

using functype = int(*)(int);
functype fp = [](int tv){return tv};
cout<<fp(17)<<endl;

语法糖是指基于语言现有的特性,构建出一个东西,程序员用起来会很方便。但它没有增加语言的原有功能。lambda表达式可看成定义仿函数闭包(函数中的函数)的语法糖。

20.8.6 lambda表达式再演示和优点总结

  1. for_each中的lambda表达式
    for_each是函数模板,配合函数对象使用,第三个参数就是一个函数对象(可以给进去lambda表达式)。
vector<int> myvector = {10, 20, 30, 40, 50};
int sum = 0;
for_each(myvector.begin(), myvector.end(), [&sum](int val){
	sum += val;
	cout<<val<<endl;
});
cout<<sum<<endl;
  1. find_if中的lambda表达式
vector<int> myvector = {10, 20, 30, 40, 50};
auto result = find_if(myvector.begin(), myvector.end(), [](int val){
	cout<<val<<endl;
	return false;//返回false,find_if会不停遍历myvector,直到返回true
});
vector<int> myvector = {10, 20, 30, 40, 50};
auto result = find_if(myvector.begin(), myvector.end(), [](int val){
	if(val>15)
		return true;
	return false;//返回false,find_if会不停遍历myvector,直到返回true
});

if(result == myvector.end())
	cout<<"not find"<<endl;
else
	cout<<"find"<<*result<<endl;

lambda表达式优点:代码简洁、灵活、强大,提高开发效率、可维护性等。

20.9 lambda表达式捕获模式的陷阱分析和展示

20.9.1 捕获列表中的&

引用捕获会导致lambda表达式(闭包)包含绑定到局部变量的引用。

#include <ctime>
std::vector<std::function<bool(int)>> gv;

void myfunc(){
	srand((unsigned)time(NULL));
	int tmpvalue = rand() % 6;
	gv.push_back([&](int tv){
		return tv % tmpvalue == 0;//tmpvalue局部变量,引用悬空
	});
}

myfunc();
cout<<gv[0](10)<<endl;//error,tmpvalue已被销毁

20.9.2 形参列表可以使用auto

c++14允许lambda的形参列表使用auto

	gv.push_back([=](auto tv){
		return tv % tmpvalue == 0;//tmpvalue会复制一份
	});

20.9.3 成员变量的捕获问题

class AT{
public:
	void addItm(){
		gv.push_back([=](auto tv){//=实际等于this
			return tv % tmpvalue == 0;//tmpvalue实际是this->tmpvalue, 不会复制一份
		});
	}
	int tmpvalue = 7;
};

AT *pat = new AT();
pat->addItem();
delete pat;
cout<<gv[0](10)<<endl;//error,AT类中tmpvalue已被销毁
	void addItm(){
		auto tmpvalueCopy = tmpvalue;
		gv.push_back([tmpvalueCopy](auto tv){
			return tv % tmpvalueCopy == 0;//tmpvalueCopy赋值, 不是类AT的成员变量
		});
	}

20.9.4 广义lambda捕获

	void addItm(){
		gv.push_back([tmpvalueCopy = tmpvalue](auto tv){//tmpvalue复制到闭包里来
			return tv % tmpvalueCopy == 0;
		});
	}

20.9.5 静态局部变量

静态局部变量不能被捕获,但能在lambda表达式中使用。

void myfunc(){
	static int tmpvalue;
	srand((unsigned)time(NULL));
	tmpvalue = rand() % 6;
	gv.push_back([](auto tv){//[],static不需要捕获
		return tv % tmpvalue == 0;
	});
}
void myfunc(){
	static int tmpvalue = 4;
	gv.push_back([](auto tv){//[],static不需要捕获
		cout<<tmpvalue<<endl;
		return tv % tmpvalue == 0;
	});
	tmpvalue++;
}

myfunc();
gv[0](10);//5

myfunc();
gv[0](10);//6
gv[1](10);//6

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

20.10.1 可变参数函数

initializer_list处理非固定参数,但类型相同。

20.10.2 initializer_list(初始化列表)

参数数量不固定,参数类型相同。
initializer_list中元素是常值,不能被改变。

initializer_list<int> myarray;
initializer_list<int> myarray2 = {12, 14, 16, 20, 30};
  1. begin、end、size
void printvalue(initializer_list<string> tmpstr){
	for(auto beg = tmpstr.begin(); beg != tmpstr.end(); ++beg)
		cout<<beg->c_str()<<endl;
	cout<<tmpstr.size()<<endl;
}

void printvalue2(initializer_list<string> tmpstr){
	for(auto& tmpitem : tmpstr)
		cout<<tmpitem.c_str()<<endl;
	cout<<tmpstr.size()<<endl;
}
printvalue({"aa", "bb", "cc"});

void printvalue(initializer_list<string> tmpstr, int tmpvalue){...}
printvalue({"aa", "bb", "cc"}, 6);
  1. 复制和赋值
    复制和赋值initializer_list时,共享列表中的元素。
initializer_list<string> myarray3 = {"aa", "bb", "cc"};
initializer_list<string> myarray4(myarray3);
initializer_list<string> myarray5;
myarray5 = myarray4;
  1. 作为构造函数参数
class CT{
public:
	CT(initializer_list<int> &tmpvalue){}
};

CT ct1 = {10, 220, 30, 40}; //隐式类型转换,explicit可禁止
CT ct2{10, 220, 30, 40};
CT ct3 = CT({10, 220, 30, 40});
  1. 统一初始化
int myarray[] = {1, 5, 6, 9};
int myarray2[]{1, 5, 6, 9};
vector<int> myvec = {1, 5, 6, 9};

编译器看到大括号括起来的形如{“aa”, “bb”, “cc”}的内容,一般会转化为std::initializer_list。

20.10.3 省略号形参(…)

可变参数函数必须至少有一个普通参数。

#include <stdarg.h>

double average(int num, ...){
	va_list valist;
	double sum = 0;
	va_start(valist, num);
	for(int i=0; i<num; i++)
		sum += va_arg(valist, int);
	va_end(valist);
	return sum/num;
}

cout<<average(3, 100, 200, 300)<<endl;
void funcTest(const char *msg...){
	int csgs = atoi(msg);
	va_list valist;
	va_start(valist, msg);
	for(int paramcount = 0; paramcount < csgs; paramcount++) {
		char *p = va_arg(valist, char*);
		printf("%d: %s\n", paramcount, p);
	}
	va_end(valist);
}

注意点:
(1)至少一个有效形参;
(2)省略号形参只能出现在最后的位置;

void myfunc(参数列表, ...);

编译器会对参数列表进行类型检查,函数调用时,省略号对应实参不会进行类型检查。
(3)省略号前逗号可以省略;

void funcTest(const char *msg, ...);
void funcTest(const char *msg...);

(4)多个非可变参时,va_start(valist, msg)绑…前那个形参;

void testFunc(char *pszDest, int DestLen, const char *pszFormat...){}
va_start(valist, pszFormat);

20.11 萃取技术概念与范例等

20.11.1 类型萃取简介

type traits,萃取技术一种可用于编译器根据类型作判断的泛型编程技术。

类型萃取定义了一个编译期间的基于模板的接口,用来查询或者修改类型的属性,可以根据这些类型萃取接口来获取各种信息。
(1)主要类型种类

is_void
is_null_pointer
is_integral
is_floating_point
is_array
is_enum
is_union
is_class
is_function
is_pointer
is_lvalue_reference
is_rvalue_reference
is_member_object_pointer
is_member_function_pointer

(2)复合类型种类

is_fundamental
is_arithmetic
is_scalar
is_object
is_compound
is_reference
is_member_pointer

(3)类型属性

is_const
is_volatile
is_trivial
is_trivially_copyable
is_standard_layout
is_pod
is_literal_type
has_unique_object_representations
is_empty
is_polymorphic
is_abstract
is_final
is_aggregate
is_signed
is_unsigned
is_bounded_array
is_unbounded_array

(4)支持的操作
trivially(平淡的,可有可无的)信息,若构造函数无初始化,就属于trivially。

is_constructible
is_trivially_constructible
is_nothrow_constructible

is_default_constructible
is_trivially_default_constructible
is_nothrow_default_constructible

is_copy_constructible
is_trivially_copy_constructible
is_nothrow_copy_constructible

is_move_constructible
is_trivially_move_constructible
is_nothrow_move_constructible

is_assignable
is_trivially_assignable
is_nothrow_assignable

is_copy_assignable
is_trivially_copy_assignable
is_nothrow_copy_assignable

is_move_assignable
is_trivially_move_assignable
is_nothrow_move_assignable

is_destructible
is_trivially_destructible
is_nothrow_destructible

has_virtual_destructor

is_swappable_with
is_swappable
is_nothrow_swappable_with
is_nothrow_swappable

20.11.2 类型萃取范例

template<typename T>
void printTraitsInfo(const T& t){
	cout<<"type name: "<<typeid(T).name()<<endl;
	cout<<"is_void = "<<is_void<T>::value<<endl;
	cout<<"is_class = "<<is_class<T>::value<<endl;
	cout<<"is_object = "<<is_object<T>::value<<endl;

	//普通类,只包含成员变量,不包含成员函数
	cout<<"is_pod = "<<is_pod<T>::value<<endl;
	cout<<"is_default_constructible = "<<is_default_constructible<T>::value<<endl;
	cout<<"is_copy_constructible = "<<is_copy_constructible<T>::value<<endl;
	cout<<"is_move_constructible = "<<is_move_constructible<T>::value<<endl;
	cout<<"is_destructible = "<<is_destructible<T>::value<<endl;

	//虚函数	
	cout<<"is_polymorphic = "<<is_polymorphic<T>::value<<endl;
	cout<<"is_trivially_default_constructible = "<<is_trivially_default_constructible<T>::value<<endl;

	//虚析构函数
	cout<<"has_virtual_destructor = "<<has_virtual_destructor<T>::value<<endl;
}

class A{
public:
	A() = default;
	A(A&& ta) = delete;
	A(const A& ta) = delete;
	virtual ~A(){}
};

class B{
public:
	int m_i;
	int m_j;
};

class C{
public:
	C(int t){}
};

printTraitsInfo(int());
printTraitsInfo(string());
printTraitsInfo(A());
printTraitsInfo(B());
printTraitsInfo(C());
printTraitsInfo(list<int>());
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值