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.