C++ 模板法实现进程创建和占位符placeholders应用的奇妙反应


模板类的用法(子进程的创建和打入函数)

先上代码

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:我的需求其实是实现进程的外部初始化,通过打入函数确定子进程实际的功能。
先画个图从顶层往下

创建进程控制类
对象初始化FunctionBase*
完成Function
调用
FunctionBase*生成对象
模板方式初始化
调用
子进程启动
在子进程中运行
main
process
FunctionBase*
Function
setEntryFunction
m_binder
process Start
实际运行绑定在m_binder的sum函数
子进程结束

所以整个逻辑如上图所示
特别的一些解释:

  • 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。
  • 在使用过程中也要注意区分是否可以使用占位符
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值