preface
Previous examples of adders One article makes it easy for you to master multiple paradigms The packaging differences of various paradigms were explained, and many children's shoes were read. This time, I will continue to explain the differences between these programming paradigms through the encapsulation of processes.
Structural design
Relative to pthread_ The create () function and the fork function are weaker. But it doesn't matter. We'll strengthen it right away.
#include <iostream> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> using namespace std; typedef void (*PROCESS)(void *pContext); pid_t CreateProcess(PROCESS child, void *pContext) { pid_t pid = fork(); if(0 == pid) { child(pContext); exit(0); } else { return pid; } } void MyChild(void *pContext) { long i = (long)pContext; cout << "In child: " << i << endl; } int main() { long i = 94; CreateProcess(MyChild, (void *)i); cout << "In Father" << endl; return 0; }
Let's first look at the main function. The parent PROCESS calls the CreateProcess function, which passes two parameters: the function address MyChild and the user-defined integer variable i. The CreateProcess function accepts two parameters: a child of PROCESS type and a pointer of void type. The latter is actually the custom parameter passed by the parent PROCESS to the child PROCESS. Similar to pthread_ The fourth parameter of create. The former PROCESS is a type of function pointer, which specifies that the function has no return value and only receives a void * parameter. In fact, the new PROCESS is also the business logic of the child PROCESS, which should be encapsulated in the function corresponding to this parameter.
Well, let's see that the CreateProcess function first calls the fork function to create a child process, and then judge whether the return value of the fork function is 0, that is, judge whether it is running in the child process. If yes, call the child function. The child here is actually MyChild. In this way, the child process jumps to the MyChild function to start running. In this function, the child process prints out the parameters received.
The CreateProcess function is the result of encapsulating the fork function according to the idea of structured design. Of course, we imitate pthread_ The result of the create function. The function using CreateProcess is obvious. When learning fork, many students can't remember whether the fork function returns 0 in the parent process or 0 in the child process Using the CreateProcess function can effectively encapsulate change points. If a subprocess wants to execute with another business logic, it only needs to write a function that encapsulates the logic and make the function meet the prototype requirements. Of course, CreateProcess also has its own limitations. This limitation, like many structured programming paradigms, does not combine data and data operation. For example, after creating a child process, you need to wait for it to die. What should you do now? After the parent process calls the CreateProcess function, it needs to keep its return value. Normally, this return value is the ID of the child process. Then, when the child process dies, it is hoped to call the waitpid function with the previous ID. in this process, the parent process needs to save the child process ID. if the ID can be associated with the relevant functions operating the child process, it is very convenient for developers. Unfortunately, the results encapsulated according to the structured programming idea can not do this at present.
Object based design
Since we have mentioned the limitation of the CreateProcess function, there is no way to combine the process related data with the method of operating the process. The object-based method can just meet this requirement.
class Process { public: Process(); ~Process(); int Run(void *pContext); int WaitForDeath(); private: int StartFunctionOfProcess(void *pContext); private: Process(const Process&); Process& operator=(const Process&); private: pid_t m_ProcessID; };
Here we give the declaration of the Process class. According to the declaration, the Process class has a member variable m_ProcessID, that is, the ID of the child Process. There are also three member functions, namely the public Run method, WaitForDeath method and the private StartFunctionOfProcess method. The Run method is to create a new Process, while the WaitForDeath method is to wait for the new Process to die. So what is the purpose of this StartFunctionOfProcess method? Let's take a look at the implementation of the Run method
int Process::Run(void *pContext) { m_ProcessID = fork(); if(m_ProcessID == 0) { m_ProcessID = getpid(); StartFunctionOfProcess(pContext); exit(0); } else if(m_ProcessID == -1) { return -1; } else return 0; }
In the implementation of the run method, the parent process calls the fork function to create a child process, and saves the ID of the child process in M_ In ProcessID, in the sub process, the getpid function is called to save its own ID, then the StartFunctionOfProcess function is called for the specific business processing. The Run method and the StartFunctionOfProcess method have a void* type parameter, that is, the process creator can pass the custom parameters to the child process. The WaitForDeath function is relatively simple. It simply calls the waitpid function to wait for the death of the child process. It is defined as follows:
int Process::WaitForDeath() { if(m_ProcessID == -1) return -1; if(waitpid(m_ProcessID, 0, 0) == -1) return -1; else return 0; }
Compared with structured code, the encapsulation of this class implements the encapsulation of data and methods of operating data. Here, the child process ID and the method of operating the child process are encapsulated together. For example, when the parent process needs to wait for the child process to die, it only needs to call the WaitForDeath function instead of saving the process ID and using the waitpid function. Of course, object-based also has great defects, that is, when the process needs to execute different business logic, it can not guarantee the closeness of the code. The StartFunctionOfProcess function encapsulates the business logic to be executed by the child process. When other business logic needs to be introduced, the function will be modified and the code closure will be destroyed. At this time, object-oriented encapsulation comes into play.
Object oriented encapsulation
The object-oriented version of Process encapsulation is to change the business logic function StartFunctionOfProcess that wraps the Process into a pure virtual function. Overridden by a derived class of Process. In this way, the business logic of the new Process is no longer placed in the Process, but in the derived class. When you need to add new business logic, you only need to add a derived class of Process without changing Process. Let's look at the code
class Process { public: Process(); virtual ~Process(); virtual int Run(void *pContext); virtual int WaitForDeath(); protected: virtual int StartFunctionOfProcess(void *pContext) = 0; private: Process(const Process&); Process& operator=(const Process&); protected: pid_t m_ProcessID; };
The declaration of the Process class in the object-oriented version has not changed much compared with the previous version, mainly programming the StartFunctionOfProcess function as a pure virtual function. This function is the entry function of the Process and encapsulates the business logic to be executed by the child Process. When you need to add the business logic executed by the child Process, you can rewrite the pure virtual function StartFunctionOfProcess according to the following example without modifying the base class Process, so as to meet the closeness of the code.
class MyProcess : public Process { public: virtual int StartFunctionOfProcess(void *pContext) { cout << (long)pContext << endl; return 0; } };