C++模板类作为类指针成员(指向普通函数和类内静态函数,相当于类处理函数)和运算符重载函数以及占位符一起使用产生的参数无法匹配问题解决办法——分家!

一、模板类作为进程类的指针成员(指向进程运行过程函数的地址)

我们在这里定义了三个类:函数基类CFuncitonBase,模板函数类CFunction(公有继承CFuntionBase),以及进程类Myprocess。
目的是实现进程函数的外部初始化:通过Myprocess类内的SetFunction模板函数和模板函数类CFunction的构造函数相配合,在外部确定类对象的实际运行函数。调用过程如下图所示:
C++模板类作为类指针成员(指向普通函数和类内静态函数,相当于类处理函数)和运算符重载函数以及占位符一起使用产生的参数无法匹配问题解决办法——分家!_第1张图片

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
class CFunction:public CFunctionBase {
public:
    ~CFunction() {}
    int operator()() {return m_binder();}
	CFunction(_FUNCTION_ func, _ARGS_... args)
        :m_binder(std::forward<_FUNCTION_>(func), std::forward<_ARGS_>(args)...) {
    }
     typename std::_Bindres_helper::type m_binder;
};

class MyProcess {
private:
    CFunctionBase* m_func = nullptr;
public:
    template
    int SetFunction(_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);
        }
        //注意父进程还会从这里运行下去,这个过程是异步的,相当于if(pid>0) return;   
    }

public:
    MyProcess() {}
    ~MyProcess() { m_func = nullptr; }
};

int main() {
    MyProcess process;
    process.SetFunction(sum,1,2);
    process.Start();

    return 0;
}

注意点

  1. std::bind函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。bind的思想实际上是一种延迟计算的思想,将可调用对象保存起来,然后在需要的时候再调用。而且这种绑定是非常灵活的,不论是普通函数、函数对象、还是成员函数都可以绑定,而且其参数可以支持占位符,比如你可以这样绑定一个二元函数auto f = bind(&func, _1, _2);,调用的时候通过f(1,2)实现调用。
  2. Myprocess类内存在一个CFunctionBase* my_func指针对象(若CFunctionBase存在纯虚函数则为抽象类,那么也不能创建实例对象),Myprocess类通过构造函数初始化时为空,但在SetFunction时将其派生类CFuntion构造的函数对象地址存入。
  3. SetFunction的函数需要是类外普通函数(可以直接将函数名传入)或者类内静态成员函数(类内函数不会自动转换为地址,必须加上取地址符&)

占位符

c++11中有29个占位符,分别是_1~_29,一般情况下是写std::placeholders::_1这样子的。占位符的作用就是用来代表参数的,std::placeholders::_1表示的是std::bind得到的std::function对象被调用时,传入的第一个参数,而std::placeholders::_2则是第二个参数。
我们调整std::placeholders::_x在std::bind时的顺序,就可以起到调整参数顺序的作用了。此外,我们也可以在std::bind的时候不用std::placeholders::_x,而直接写成固定的值,这样子调用std::function存储的对象时,对应位置的参数将是固定值。

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;//output: 2,1
    return 0;
}

C++模板类,模板函数,运算符重载函数以及占位符一起使用的化学反应

当我创建一个进程对象并已经将一个普通函数/静态函数设置为该进程的运行函数,如果在运行过程中我还需要进一步再提供部分数据传入这个函数内部,那么原先设置好的特征标/参数列表已不能再满足我的需求。那么我可以通过占位符为CFunction模板函数构建时再增加一部分不清楚具体内容的特定类型参数,可运行代码如下:
举个例子:一个服务器进程,循环等待客户的连接。等到接收到客户的连接,送给线程池处理(服务器在处理客户端的连接一般是事先初始化多个线程,而不是遇到连接再创建线程,换句话说,线程的创建也是消耗资源和时间的)。这个过程中,线程并不事先知道用户的连接信息(一般是一个socket内容),那么在处理任务时,就需要将这个套接字传入,或者再传入一些额外字符串数据。

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()(int b) { return 0; };//替换了之前的virtual int operator()() { return 0; };如果保留会报错
};

template
class CFunction :public CFunctionBase {
public:
	~CFunction() {}
	virtual int operator()(int b) {  //替换了之前的int operator()() {return m_binder();}如果保留会报错
		return m_binder(b);
	}
	CFunction(_FUNCTION_ func, _ARGS_... args)
	:m_binder(std::forward<_FUNCTION_>(func), std::forward<_ARGS_>(args)...) {
	}
	typename std::_Bindres_helper::type m_binder;
    }
};


class MyProcess {
private:
    CFunctionBase* m_func = nullptr;
public:
    template
    int SetFunction(_FUNCTION_ func, _ARGS_... args) {
        m_func = new CFunction<_FUNCTION_, _ARGS_...>(func, args...);
        return 0;
    }
    int Start(int b) {//Start传入int
        pid_t pid = fork();
        if (pid == 0) {
            (*m_func)(b);//在运算符重载函数中传入b
            exit(0);
        }
    }
    
private:
    CFunctionBase* m_func = nullptr;
public:
    //其他不变
    
};

int main() {
    MyProcess process;
    process.SetEntryFunction(sum,1, std::placeholders::_1);
    process.Start(4);
    return 0;
}

如果之前CFunctionBase和CFunction的不带参数()重载运算符继续保留,即:

class CFunctionBase {
private:
public:
    virtual ~CFunctionBase() {}
    virtual int operator()() { return 0; };
    virtual int operator()(int b) { return 0; };
};
template
class CFunction :public CFunctionBase {
public:
	~CFunction() {}
	virtual int operator()() {return m_binder();}
	virtual int operator()(int b) {  //替换了之前的如果保留会报错
		return m_binder(b);
	}
	CFunction(_FUNCTION_ func, _ARGS_... args)
	:m_binder(std::forward<_FUNCTION_>(func), std::forward<_ARGS_>(args)...) {
	}
	typename std::_Bindres_helper::type m_binder;
    }
};

这时使用编译器生成可执行文件将报错如下所示:
C++模板类作为类指针成员(指向普通函数和类内静态函数,相当于类处理函数)和运算符重载函数以及占位符一起使用产生的参数无法匹配问题解决办法——分家!_第2张图片
std::_Mu:用于匹配占位符的模板类

std::_Mu< _Arg, _IsBindExp, _IsPlaceholder >

Detailed Description
template::value, bool _IsPlaceholder = (is_placeholder<_Arg>::value > 0)>
class std::_Mu< _Arg, _IsBindExp, _IsPlaceholder >"Maps an argument to bind() into an actual argument to the bound function object [func.bind.bind]/10. Only the first parameter should be specified: the rest are used to determine among the various implementations. Note that, although this class is a function object, it isn’t entirely normal because it takes only two parameters regardless of the number of parameters passed to the bind expression. The first parameter is the bound argument and the second parameter is a tuple containing references to the rest of the arguments.
类功能:将 bind() 的参数映射到绑定函数对象的实际参数。只应指定第一个参数:其余参数用于确定各种实现。请注意,尽管此类是一个函数对象,但它并不完全正常,因为无论传递给绑定表达式的参数数量如何,它都只接受两个参数。第一个参数是绑定参数,第二个参数是包含对其余参数的引用的元组。
std::is_bind_expression用于判断是否是bind表达式,有value成员,返回值是true或false
std::is_placeholder 用于判断T是否为占位符,它有一个成员变量value。如果T是placeholder类型,value的值为1代表 _1,2代表 _2;如果T不是,则value为0。

我们可以看出对于operator()()无参运算符重载函数,std::_Mu函数无法找到可以匹配的参数对象,即占位符std::Placehoder_1无法从现有的参数列表中找到合适的目标,因存在这种无法正确调用的函数,所以函数无法通过编译。
换句话说,我们可以将其简单化为如下代码(参考C++ 模板法实现进程创建和占位符placeholders应用的奇妙反应):

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;
}

在sum需要b运算符时,占位符1从func中找不到参数,因而报错如下:
C++模板类作为类指针成员(指向普通函数和类内静态函数,相当于类处理函数)和运算符重载函数以及占位符一起使用产生的参数无法匹配问题解决办法——分家!_第3张图片
C++模板类作为类指针成员(指向普通函数和类内静态函数,相当于类处理函数)和运算符重载函数以及占位符一起使用产生的参数无法匹配问题解决办法——分家!_第4张图片
因此,对于提前告诉进程对象Myprocess A.SetEntryFunction(sum,1, std::placeholders::_1);,编译器在编译时发现在空参数()重载运算符执行时,无法正确匹配参数,因而存在这种风险Error,就算你后面不会在这个对象调用这个空参数方法,也编译不通过。当然你可以在空参数括号重载运算符内直接输入一个常数作为参数:int operator()() {return m_binder(123456);},但这样这个函数就失去了本身存在的意义。

解决方法-分家!

我们可以为需要传入额外参数的对象重新定义一个继承自CFunctionBase类的CFuntion-N代,不同类定义不同参数的()运算符重载函数(但是,你需要在CFunctionBase类定义时为所有派生类的运算符重载函数都定义一个无意义但让编译通过的同名同参数虚函数,所有派生类CFunction-?代的函数都需要定义),如下所示:

class CFunctionBase {
private:
public:
    virtual ~CFunctionBase() {}
    virtual int operator()() { return 0; };//return -1/0均可
    virtual int operator()(int P1) { return 0; };
    virtual int operator()(int P1,int P2) { return 0; };
};
template
class CFunction :public CFunctionBase {
public:
    CFunction(_FUNCTION_ func, _ARGS_... args)
        :m_binder(std::forward<_FUNCTION_>(func), std::forward<_ARGS_>(args)...) {}
    typename std::_Bindres_helper::type m_binder;
    virtual ~CFunction() {}
    virtual int operator()() { return m_binder(); }
private:
};
template
class CFunction_1:public CFunctionBase {
public:
     CFunction_1(_FUNCTION_ func, _ARGS_... args)
        :m_binder(std::forward<_FUNCTION_>(func), std::forward<_ARGS_>(args)...) {}
    typename std::_Bindres_helper::type m_binder;
    virtual ~ CFunction_1() {}
    virtual int operator()(int P1) {
        return m_binder(P1);
    }
private:
}
template
class  CFunction_2:public CFunctionBase {
public:
    CFunction_2(_FUNCTION_ func, _ARGS_... args)
        :m_binder(std::forward<_FUNCTION_>(func), std::forward<_ARGS_>(args)...) {}
    typename std::_Bindres_helper::type m_binder;
    virtual ~ CFunction_2() {}
    virtual int operator()(int P1,int P2) {
        return m_binder(P1,P2);
    }
private:
};

注意点

  • 各自继承CFuncionBase,各自实现各自的小括号重载方法。特殊的需要用占位符的为SpecialCFunction;一般的不需要占位符的为CFunction。
  • 作为父亲的CFuntionBase类要为所有儿子的重载运算符定义相同特征标和同名函数(内容不管)。
  • 在MyProcess中区分CFunction和SpecialCFunction实现不同的SetEntryFunction。
  • 在使用过程中也要注意区分是否可以使用占位符。

你可能感兴趣的:(c++,开发语言)