目录
一、抛砖
看到一个架构里的部分代码,觉得挺有意思,拖出来鞭打一下
class Base
{
public:
Base(int x,std::function<int()> action):myaction(action)
{
printf("Base::初始化=%d\n",x);
}
void Base_do()
{
printf("Base::Base_do()\n");
myaction();
}
std::function<int()> myaction;
};
class Son :public Base
{
public:
Son(int x,int(Son::*action)()):Base(x+1,bind(action,*this))
{
printf("Son::初始化=%d\n",x);
m_x=x;
}
int m_x=0;
int Son_real_action()
{
printf("Son::Son_real_action()\n");
}
};
class myqueue
{
public:
void run(){printf("myqueue(run)::m_a=%d\n",m_a);}
map<string, function<Base*(int)>> BMap;
template<typename T>
void insert(string name,int(T::*action)())//q->insert(name,&Son::func);
{
BMap.emplace(make_pair(name, [=](int x){ return new T(x,action);} ));
}
int m_a=-1;
};
int main()
{
string index ="1";
myqueue * q=new myqueue();
q->insert<Son>(index,&Son::Son_real_action);
q->BMap.begin()->second(99)->Base_do();
printf("getchar()\n");
getchar();
return 0;
}
你能知道打印的顺序是怎么样的吗?这里涉及到几个知识点,分别是lambda,function,bind,已经函数指针指向成员函数。如果看不懂就往下看看各关键字的使用。函数指针指向成员函数这里不多讲了,其实就是这样来定义:
int (Son::*pfun1)()=&Son::func;
二、lambda
1、价值体现
老的方式
void hello()
{
printf("hello ...");
}
void main()
{
thread t1(hello);
t1.join();
}
lambda方式
void main()
{
thread t1([]() {
printf("hello...");
});
t1.join();
}
lambda用于定义并创建匿名的函数对象,实际上就是一个仿函数,主要是作为内嵌函数,逻辑更加清楚,代码可读性更好。能让你的函数简捷的写出来。适合函数内容少、复用少的情况。
2、Lambda的语法:
[捕获](参数列表) mutable ->return-type{statement} 或者是
[函数对象参数] (操作符重载函数参数) mutable 或 exception 声明 -> 返回值类型 {函数体}
(1)捕获/函数对象参数
标识一个 Lambda 表达式的开始,这部分必须存在,不能省略。是针对参数传入的关键字,它分传值和引用两种方式,这2者的区别是,引用在参数前面&。
如
- 空。没有任何函数对象参数。
- =。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
- &。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是引用传递方式(相当于是编译器自动为我们按引用传递了所有局部变量)。
- this。函数体内可以使用 Lambda 所在类中的成员变量。
- a。将 a 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝,因为默认情况下函数是 const 的,要修改传递进来的拷贝,可以添加 mutable 修饰符。
- &a。将 a 按引用进行传递。
- a,&b。将 a 按值传递,b 按引用进行传递。
- =,&a,&b。除 a 和 b 按引用进行传递外,其他参数都按值进行传递。
- &,a,b。除 a 和 b 按值进行传递外,其他参数都按引用进行传递。
{
int a = 2;//待修改值
int x = 9;//传参值
int r=-1;//返回值
auto func3 = [&](int t)->int {return a += t;};//a x r = 11 9 11//引用传递时,可以修改父作用域的值本身
r=func3(x);printf("a,x,r = %d %d %d\n",a,x,r);
}
{
int a = 2;//待修改值
int x = 9;//传参值
int r=-1;//返回值
auto func4 = [=](int t)->int {return a += t;};//err//值传递时,想修改父作用域的值失败
r=func4(x);printf("a,x,r = %d %d %d\n",a,x,r);
}
(2)参数列表/操作符重载函数参数
和c语言的参数语法一样,不做解释。标识重载的 () 操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如: (a, b))和按引用 (如: (&a, &b)) 两种方式进行传递。
{
int a = 2;//待修改值
int x = 9;//传参值
int r=-1;//返回值
auto func5 = [&](int &t)->int {t=3;return t;};//2 3 3 //参数只有引用能修改值x本身
r=func5(x);printf("a,x,r = %d %d %d\n",a,x,r);
}
{
int a = 2;//待修改值
int x = 9;//传参值
int r=-1;//返回值
auto func6 = [=](int t)->int {t=3;return t;};//2 9 3 //参数 传递值 不能修改值x本身
r=func6(x);printf("a,x,r = %d %d %d\n",a,x,r);
}
(3)mutable、exception
修饰符,这部分可以省略。函数对象参数 按值传递时,默认情况lambda是const 函数,在添加mutable后,就会取消其常量属性。能对父作用域的成员变量做修改操作(注意是能修改拷贝,而不是值本身,如果想修改值本身,需要使用引用传递)。如果是传值操作但是想修改父作用域传入变量时,不加mutable会报错。
exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。
{
int a = 2;//待修改值
int x = 9;//传参值
int r=-1;//返回值
auto func1 = [=](int t)mutable->int {return a += t;};//a x r = 2 9 11//值传递时,只有加入mutable,才能修改父作用域的值的拷贝'a',而值a本身不会被修改
r=func1(x);printf("a,x,r = %d %d %d\n",a,x,r);
}
{
int a = 2;//待修改值
int x = 9;//传参值
int r=-1;//返回值
auto func2 = [&](int t)mutable->int {return a += t;};//a x r = 11 9 11//只有引用传递能修改父作用域的值a本身
r=func2(x);printf("a,x,r = %d %d %d\n",a,x,r);
}
(4)->return-type:/返回值类型
这是lambda函数的返回类型。注意这个是一体的,如果需要省略这块代码全部省略。省略后,c++会自动推导返回类型,默认是int.标识函数返回值的类型,当返回值为 void,或者函数体中只有一处 return 的地方(此时编译器可以自动推断出返回值类型)
时,这部分可以省略。
{
int a = 2;//待修改值
int x = 9;//传参值
int r=-1;//返回值
auto func7 = [=](int t)->bool {return a;};//2 9 1//返回类型修改
r=func7(x);printf("a,x,r = %d %d %d\n",a,x,r);
}
(5)statement/函数体
具体的函数实现。标识函数的实现,这部分不能省略,但函数体可以为空。
讲完了,来看看个实战使用
std::map<string,testlam*> testMap;
//方式1
auto func = [=]{ return new testlam(a);};
testMap.emplace(make_pair(name, func() ));
//方式2,更直接
testMap.emplace(make_pair(name, [&]{ return new testlam(a);}() ));
方式2更直接,这样就不用额外写个具体函数了。
三、function
类模板std::function是一个可调用对象包装器,可以容纳所有可调用对象,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟它们的执行。std::function可以存储,复制和调用任何Callable 目标的实例,例如函数,lambda表达式,绑定表达式或其他函数对象,以及指向成员函数和指向数据成员的指针。
1、定义格式
std::function<返回值(参数类型)>,如:std::function<char* (int)>
2、作用
std::function可以取代函数指针的作用,因为它可以延迟函数的执行,特别适合作为回调函数使用。它比普通函数指针更加的灵活和便利。
3、使用方式
我定义了一个std::function< int(int)> Functional;指定了返回值是int,参数是int的函数
#include <functional>
#include <iostream>
using namespace std;
std::function< int(int)> Functional;
// 普通函数
int TestFunc(int a)
{
return a;
}
// Lambda表达式
auto lambda = [](int a)->int{ return a; };
// 仿函数(functor)
class Functor
{
public:
int operator()(int a)
{
return a;
}
};
// 1.类成员函数
// 2.类静态函数
class TestClass
{
public:
int ClassMember(int a) { return a; }
static int StaticMember(int a) { return a; }
};
int main()
{
// 普通函数
Functional = TestFunc;
int result = Functional(10);
cout << "普通函数:"<< result << endl;
// Lambda表达式
Functional = lambda;
result = Functional(20);
cout << "Lambda表达式:"<< result << endl;
// 仿函数
Functor testFunctor;
Functional = testFunctor;
result = Functional(30);
cout << "仿函数:"<< result << endl;
// 类成员函数
TestClass testObj;
Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);
result = Functional(40);
cout << "类成员函数:"<< result << endl;
// 类静态函数
Functional = TestClass::StaticMember;
result = Functional(50);
cout << "类静态函数:"<< result << endl;
return 0;
}
可见十分的灵活呀。
lambda配合function来声明函数,也是很好的一个搭配,可以看上面代码lambda实战代码里有一个缺点是,传入的参数a已经在插入的时候就已经定了,如果我们想在用的时候才传参进去怎么办?这个时候就可以用function来解决,这个也灵活体现了C++的特性,看代码:
map<string, function<testlam*()>> hMap;
//如果类似上面lambda的普通用法
int a =9;
hMap.emplace(make_pair(name, [&]{ return new testlam(a);} ));//区分于上面的lambda,相当传了一个函数指针
hMap.begin()->second();
//新用法:想在后面再传参
hMap.emplace(make_pair(name, [&](int x){ return new testlam(x);} ));//注意这里加了传参(int x)
hMap.begin()->second(a);//在调用的时候,才传入参数
四、bind
来看下function的配套工具bind
可将std::bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
std::bind将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function保存。std::bind主要有以下两个作用:
-将可调用对象和其参数绑定成一个仿函数;
-只绑定部分参数,减少可调用对象传入的参数。
1、定义格式
std::bind<函数名,对象指针,参数1,参数2....)
2、怎么用
记住几种用法
(1)std::bind绑定普通函数
bind的第一个参数是函数名,普通函数做实参时,会隐式转换成函数指针。因此std::bind (my_divide,_1,2)等价于std::bind (&my_divide,_1,2);
_1表示占位符,位于<functional>中,std::placeholders::_1;
double my_divide (double x, double y) {return x/y;}
auto fn_half = std::bind (my_divide,_1,5);
std::cout << fn_half(10) << '\n'; // 2
(2) std::bind绑定一个成员函数
struct Foo {
void print_sum(int n1, int n2)
{
std::cout << n1+n2 << '\n';
}
int data = 10;
};
int main()
{
Foo foo;
auto f = std::bind(&Foo::print_sum, &foo, 95, std::placeholders::_1);
f(5); // 100
}
(3)绑定一个引用参数
默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。但是,与lambda类似,有时对有些绑定的参数希望以引用的方式传递,或是要绑定参数的类型无法拷贝。
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
#include <sstream>
using namespace std::placeholders;
using namespace std;
ostream & print(ostream &os, const string& s, char c)
{
os << s << c;
return os;
}
int main()
{
vector<string> words{"helo", "world", "this", "is", "C++11"};
ostringstream os;
char c = ' ';
for_each(words.begin(), words.end(),
[&os, c](const string & s){os << s << c;} );
cout << os.str() << endl;
ostringstream os1;
// ostream不能拷贝,若希望传递给bind一个对象,
// 而不拷贝它,就必须使用标准库提供的ref函数
for_each(words.begin(), words.end(),
bind(print, ref(os1), _1, c));
cout << os1.str() << endl;
}
五、override 和 final-买三送二
来看一下之前的文章简单回顾多态_熊猫Ben的博客-优快云博客
1、虚函数的两个常见错误
(1)无意的重写
在派生类中声明了一个与基类的某个虚函数具有相同的签名的成员函数,不小心重写了这个虚函数。
(2)虚函数签名不匹配
虚函数签名不匹配的错误通常是因为 函数名、参数列表 或 const 属性不一样,导致意外创建了一个新的虚函数,而不是重写一个已存在的虚函数。
针对上述情况,C++ 11 增加了两个继承控制关键字:override 和 final,两者的作用分别为:
- override:在派生类中使用,保证在派生类中声明的重载函数,与基类的虚函数有相同的签名;
- final:阻止类的进一步派生 和 虚函数的进一步重写。如果不希望某个类被继承,或不希望某个虚函数被重写
六、回首掏
回到文章开头第一点的问题上来,上面打印的结果是
Base::初始化=100
Son::初始化=99
Base::Base_do()
Son::Son_real_action()
其实也不难
//将该函数Son_real_action指针传入insert里
q->insert<Son>(index,&Son::Son_real_action);
//由于BMap的second定义的是function<Son>所以会延迟实现,未来会new Son
//接着再这里second(99)的时候才真正new Son了,并且传参99和之前的Son_real_action进去
//这时打印出现了Base::初始化=100 和Son::初始化=99 ,并且将Base::myaction也指向了Son_real_action
q->BMap.begin()->second(99)->Base_do();
//所以Base_do里面调用的myaction,实质上是Son_real_action
这一块其实是在一个网络服务器上看到的。因为每个接口用的肯定都是跟接口类似的函数名,所以函数名有很多,也不相同。在insert的时候就绑定好接口函数,index就是接口名,然后根据接口名来找实现方法,在second的时候将客户端的传参之类传入就完成。我分析这样的做法是可以针对基类来调用的情况下,不用多写那么多虚函数,也方便使用时不需要知道子类的类型。
这里说一个场景:就是需要传递一个子类指针给基类调用,这样无论是Son还是哪个子类,都很兼容:参数显式,接口统一。
1、一般基类调用子类,都是Base * base=new Son()在基类设一个虚函数func(),然后子类实现func(),然后基类内部可以调这个虚函数func()。但是当子类有很多基类没有的方法时(或者说子类实现的是接口,所以接口名跟其他子类不一样),就需要在基类做很多是虚函数,而且在其他子类会多余。
2、或者是Base * base=new Son(),子类的func()方法在基类没有这个方法的时候,利用dynamic_cast<Son*>{base}->func()。这样在操作中你需要知道要用的是哪个子类才好强转,子类多的话不好实现。
3、下面这个方法不用知道子类,比如在队列里 lam->BMap,找到了这个基类,只要直接调用基类的接口就能完成对子类的调用,可以说完全不需要知道子类是谁。这样就解决了1的烦恼。不过有个缺点是每次调用都会有新子类new,不过后续可以自己加上delete就行了。
参考: