|
MPS-Basic
|
We want to prepare several pressure calculation methods for the user to choose from. But so as not to interfere with other parts of the code, they should all have a common input and output, such as taking particles as input and returning pressure as output. Otherwise, switching between classes requires changing their inputs and handling their outputs, which is troublesome. This problem is not limited to pressure calculation methods, but is always a problem when trying to provide alternative methods. How can we define multiple classes, but guarantee that the inputs and outputs of each class are identical to the other classes? The interface class is useful in such situations.
Let's take a look at the example of pressure calculation. PressureCalculator::Interface class defines how all the pressure calculator class should act.
A virtual function is a member function that is declared in a base class and is potentially overridden (redefined) in derived classes. Declaring a virtual function as = 0 makes it a pure virtual function, meaning derived classes must override this function. In the above example, all the classes that's derived from Interface class must override calc function, because it's a pure virtual function. At the same time, those new calc functions must receive vector<Particles> as an input, and return vector<double> as an output. If you take a look at PressureCalculator::Implicit::calc(), for example, you can see it follows the rule defined in this Interface class. Therefore, there is no need for us to modify any other parts of the code even when we change pressure calculation class from PressureCalculator::Implicit to PressureCalculator::Explicit, for example.
virtual ~Interface(){}; is a destructor. It doesn't have any process in this code, but it's recommended to declare it here by c++ rule.
So in short,
As an example, we define Animal class that has speak() function as a pure virtual function. Two classes, Dog and Cat, are derived from Animal class, and override speak() function.
In the main program, first we define Animal pointer. Then it's assigned to new instance of Dog or Cat.
The reason why we first defined a pointer is because in c++ it is the only way to dynamically determine the entity of a variable. Since the Animal class works as a class just like the Dog and Cat classes, if you define myAnimal as Animal class, then myAnimal will only work as an Animal class, not Dog or Cat. If we use a pointer,
myAnimal is Animal class, allowing us to call functions defined in Animal class (which is also defined in either Dog or Cat class since we use pure virtual function).Using raw pointers is dangerous because:
delete to deallocate memory manually to prevent memory leaks.delete, throwing exceptions or returning early from functions can prevent the code from reaching the delete, leading to memory leaks.From c++11, smart pointers have been introduced into standard libraries. Smart pointers allow us to use pointers safely because:
delete, but the smart pointer does it automatically.shared_ptr and weak_ptr, but we don't cover them here.Unique pointer is the sole owner of the object, meaning other pointers cannot point the same object as the unique pointer. Therefore, the code bellow causes a compile error.
To transfer the ownership of the unique pointer, meaning to change the owner of the object from a unique pointer to a different pointer, we can use std::move.
This way of transferring ownership is especially useful when we want to use a smart pointer for a function argument.
In the above example, myFunc(myPtr) doesn't work because ptr in myFunc requires an ownership that myPtr has. myFunc(std::move(myPtr)) works fine because there will the ownership will be transferred safely.
To clarify that there will be a transfer in ownership, you should add && to the argument.