模板类的用法(子进程的创建和打入函数)
先上代码
using namespace std;
int sum(int a, int b) {
cout << a << endl;
cout << b << endl;
return a + b;
}
class CFunctionBase {
private:
public:
virtual ~CFunctionBase() {}
virtual int operator()() { return 0; };
};
template<typename _FUNCTION_,typename... _ARGS_>
class CFunction:public CFunctionBase {
typename std::_Bindres_helper<int, _FUNCTION_, _ARGS_...>::type m_binder;
public:
~CFunction() {}
int operator()() {return m_binder();}
CFunction(_FUNCTION_ func, _ARGS_... args)
:m_binder(std::forward<_FUNCTION_>(func), std::forward<_ARGS_>(args)...) {
}
};
class MyProcess {
private:
CFunctionBase* m_func = nullptr;
public:
template<typename _FUNCTION_, typename... _ARGS_>
int SetEntryFunction(_FUNCTION_ func, _ARGS_... args) {
m_func = new CFunction<_FUNCTION_, _ARGS_...>(func, args...);
return 0;
}
int Start() {
pid_t pid = fork();
if (pid == 0) {
(*m_func)();
exit(0);
}
//注意父进程还会从这里运行下去,这个过程是异步的
}
public:
MyProcess() {}
~MyProcess() { m_func = nullptr; }
};
int main() {
MyProcess process;
process.SetEntryFunction(sum,1,2);
process.Start();
return 0;
}
代码解释
PS:我的需求其实是实现进程的外部初始化,通过打入函数确定子进程实际的功能。
先画个图从顶层往下
所以整个逻辑如上图所示
特别的一些解释:
- Function作为模板类,其初始化必须要规定类型,否则会出现无法匹配参数类型的错误,因此选择打入具体函数的setEntryFunction才实际创建CFuntion对象 *
- 传入参数的数量和类型一定要与sum函数一致,不然要么报参数数量错误,要么报参数类型错误。
占位符简单介绍
先上代码
int sum(int a, int b) {
cout << a << endl;
cout << b << endl;
return a + b;
}
int main() {
auto func= std::bind(sum, std::placeholders::_2, std::placeholders::_1);
std::cout << func(1, 2) << endl;
return 0;
}
观察可知,占位符常可以用于绑定函数。重点不是介绍bind,略过不讲。用起来和它的名字一样传递参数,_1对应原函数第一个参数,_2对应原函数第二个参数。再具体可以看 cppreference.com
两相结合
考虑这么一种情形,在进程或者线程运行的过程中,一部分参数由外部创建后传入。换句话说,在子进程创建的过程中,一部分参数并不知道其类型和值
举个例子:一个服务器进程,循环等待客户的连接。等到接收到客户的连接,送给线程池处理(服务器在处理客户端的连接一般是事先初始化多个线程,而不是遇到连接再创建线程,换句话说,线程的创建也是消耗资源和时间的)。这个过程中,线程并不事先知道用户的连接信息(一般是一个socket内容)。
这个时候,就需要占用符出场了
using namespace std;
int sum(int a, int b) {
//不变
}
class CFunctionBase {
private:
public:
//其余不变
//增加一个小括号重载
virtual int operator()(int b) { return 0; };
};
template<typename _FUNCTION_, typename... _ARGS_>
class CFunction :public CFunctionBase {
public:
//virtual int operator()() {return m_binder(b);}
//替换为有小括号的重载其他不变
virtual int operator()(int b) {
return m_binder(b);
}
};
class MyProcess {
private:
CFunctionBase* m_func = nullptr;
public:
//其他不变
int Start(int b) {//Start传入int
pid_t pid = fork();
if (pid == 0) {
(*m_func)(b);//直接b给上
exit(0);
}
}
};
int main() {
MyProcess process;
process.SetEntryFunction(sum,1, std::placeholders::_1);
process.Start(4);
return 0;
}
改动范围
- 修改FunctionBase对小括号的重载,增加一个输入int参数的重载
- Function中实现对小括号的重载
- SetEntryFunction中用占位符替换参数
- 在(*m_func)(b)中传入b
其实就是对小括号的重载
两相结合也有矛盾,分居吧
特别的,如果有人细心就会发现,上面合并中,是在Function中把原有的小括号重载替换掉了。那么能不能不替换,共存呢?
答案是不行
严重性 代码 说明 项目 文件 行 禁止显示状态
错误 no match for call to ‘(std::_Mu<std::_Placeholder<1>, false, true>) (std::__tuple_element_t<1, std::tuple<int, std::_Placeholder<1> > >&, std::tuple<>&)’ test C:\usr\include\c++\11\functional 571
看看这报错,都啥啥啥呀。
涉及到别人封装的内容,直接抓瞎。但是不要急。问题其实非常简单:
回到模板类的实例化
process.SetEntryFunction(sum,1, std::placeholders::_1);
当我们在该语句中使用占位符。
template<typename _FUNCTION_, typename... _ARGS_>
int SetEntryFunction(_FUNCTION_ func, _ARGS_... args) {
m_func = new CFunction<_FUNCTION_, _ARGS_...>(func, args...);
return 0;
}
该模板函数依据外部的类型传入CFunction。分别是函数sum,一个整形,一个占位符。
CFunction(_FUNCTION_ func, _ARGS_... args)
:m_binder(std::forward<_FUNCTION_>(func), std::forward<_ARGS_>(args)...) {}
typename std::_Bindres_helper<int, _FUNCTION_, _ARGS_...>::type m_binder;
而在CFunction中,通过std::forward,无条件将类型传递给到std::_Bindres_helper<int, FUNCTION, ARGS…>::type就是m_binder实现了函数与参数的绑定。
先不考虑实现,仅在逻辑上,m_binder()等效于
int sum(1,占位符1);
如此,让我们简单地复现一下问题情况:
using namespace std;
int sum(int a, int b) {
cout << a << endl;
cout << b << endl;
return a + b;
}
int main() {
auto func = std::bind(sum, 1, std::placeholders::_1);
std::cout << func() << endl;
return 0;
}
其实这个时候编译器已经轻松找到了该问题。
PS:用模板的话要当心,一些错误只会在编译过程暴露出来。当然比起在运行阶段转入后台的逻辑错误要好得多。
非要编译也会提示错误(当然由于内部实现的不同,其实过程不太一样)。但是逻辑上是对的。
分居吧
其实这个问题有个"简单"的解决方法,对于需要占位符的Funcion额外创建类型,使其继承FunctionBase或者实现了相同功能的其他base基类,并实现自己特有的小括号重载。
对于用不着占位符的Function,直接使用我们第一部分的Function方法就行,无需额外通过小括号传递其他参数。
比如:
using namespace std;
int sum(int a, int b) {
//不变
}
class CFunctionBase {
private:
public:
virtual ~CFunctionBase() {}
virtual int operator()() { return 0; };
virtual int operator()(int b) { return 0; };
};
template<typename _FUNCTION_, typename... _ARGS_>
class SpecialCFunction :public CFunctionBase {
public:
SpecialCFunction(_FUNCTION_ func, _ARGS_... args)
:m_binder(std::forward<_FUNCTION_>(func), std::forward<_ARGS_>(args)...) {}
typename std::_Bindres_helper<int, _FUNCTION_, _ARGS_...>::type m_binder;
virtual ~SpecialCFunction() {}
virtual int operator()(int b) {
return m_binder(b);
}
private:
};
template<typename _FUNCTION_, typename... _ARGS_>
class CFunction :public CFunctionBase {
public:
CFunction(_FUNCTION_ func, _ARGS_... args)
:m_binder(std::forward<_FUNCTION_>(func), std::forward<_ARGS_>(args)...) {}
typename std::_Bindres_helper<int, _FUNCTION_, _ARGS_...>::type m_binder;
virtual ~CFunction() {}
virtual int operator()() { return m_binder(); }
private:
};
class MyProcess {
private:
CFunctionBase* m_func = nullptr;
public:
template<typename _FUNCTION_, typename... _ARGS_>
int SetEntryFunction_Special(_FUNCTION_ func, _ARGS_... args) {
m_func = new SpecialCFunction<_FUNCTION_, _ARGS_...>(func, args...);
return 0;
}
template<typename _FUNCTION_, typename... _ARGS_>
int SetEntryFunction(_FUNCTION_ func, _ARGS_... args) {
m_func = new CFunction<_FUNCTION_, _ARGS_...>(func, args...);
return 0;
}
int Start(int b) {
pid_t pid = fork();
if (pid == 0) {
(*m_func)(b);
exit(0);
}
}
int Start() {
pid_t pid = fork();
if (pid == 0) {
(*m_func)();
exit(0);
}
}
public:
MyProcess() {}
~MyProcess() { m_func = nullptr; }
};
int main() {
MyProcess process;
process.SetEntryFunction_Special(sum,1, std::placeholders::_1);
process.Start(4);
process.SetEntryFunction(sum, 1, 7);
process.Start();
return 0;
}
- 各自继承CFuncionBase,各自实现各自的小括号重载方法。特殊的需要用占位符的为SpecialCFunction;一般的不需要占位符的为CFunction。
- 在MyProcess中区分CFunction和SpecialCFunction实现不同的SetEntryFunction和SetEntryFunction_Special。
- 在使用过程中也要注意区分是否可以使用占位符