I'm a new to OOP and trying to learn C and I came cross polymorphism and using the virtual keyword. I just don't understand why we might need to do that. I've checked this site for similar questions but none of them attempts to answer that why?
CodePudding user response:
The main goal is: Separation of concerns.
Let's take an example from my day job. I work on a codebase that does networking. The vast majority of the codebase depends on a class that looks like:
class Transport
{
public:
virtual bool SendMessage(int clientId, string message);
};
Imagine I've got hundred files, and tens of thousand of lines of code, using this class.
Now, my colleague in the low-level team wants to allow our code to use both UDP and TCP for communications. They can simply implement:
class UdpTransport:public Transport
{
public:
bool SendMessage(int clientId, string message) override { /* their code */};
};
class TcpTransport:public Transport
{
public:
bool SendMessage(int clientId, string message) override { /* their code */};
};
This allows me to keep the whole of my code unchanged (using only Transport*
or Transport&
) and not have to worry about what a UdpTransport
or TcpTransport
is. It allows one part of the code to work without knowing what another part of the code is doing.
CodePudding user response:
Most introductions of polymorphism start with something like this:
Base* b = new Derived(); // ouch :/
This is very unfortunate in my humble opinion, because it creates the misunderstanding that polymorphism implies manual memory managment via new
. The topics are somewhat related. Though my example of polymorphism would start like this:
#include <iostream>
struct base {
virtual void say_hello() { std::cout << "hello base\n"; }
};
struct derived : base {
void say_hello() override { std::cout << "hello derived\n";}
};
void foo(base& b) {
b.say_hello();
}
int main()
{
base b;
derived d;
foo(b);
foo(d);
}
say_hello
must be declared virtual
to allow derived
to override the method. If say_hello
was not virtual then derived::say_hello
would not override it.
If you remove virtual
and override
above you can see what happens when the method in the derived class does not override the method in the base class. The call b.say_hello()
would then call base::say_hello()
no matter if a base
or derived
is passed to foo
because only for virtual methods the method to be called considers the dynamic type of the object.
What are the use cases for a base class pointer pointing to a derived class object
Same as the reference above. foo(base&)
can take an object of any type that derives from base
and then call its say_hello
method. If this wasnt possible you would have to write a foo(derived)
, foo(derived2)
, foo(derived3)
to call their say_hello
method. In a nutshell, polymorphism means to treat different types the same. foo
does not need to know what the dynamic type of its paramter is. It only needs to know that it inherites from base
.
CodePudding user response:
Consider the following class architecture and function:
#include <iostream>
#include <string>
class Base
{
public:
std::string hello() const
{
return "Hello Base!";
}
};
class Derived : public Base
{
public:
std::string hello() const
{
return "Hello Derived!";
}
};
void outputHello(const Base& b)
{
std::cout << b.hello() << '\n';
}
We have:
- A base class with a
hello()
method that returns"Hello Base!"
; - A derived class with a
hello()
method that returns"Hello Derived!"
; - A
outputHello(const Base&)
function that takes a reference to aBase
object as its argument and prints out the result of a call to itshello()
method.
Now, what do you think the following program will output?
int main()
{
Base b;
outputHello(b);
Derived d;
// Allowed as a reference/pointer to Base can
// reference/point to a Derived object
outputHello(d);
}
You probably guessed the following if you come from Java or a similar object-oriented language:
Hello Base!
Hello Derived!
But it instead it outputs Hello Base!
twice. Why? It's actually quite simple. When the compiler gets to the outputHello
function body it sees std:cout << b.hello() << '\n'
. It thinks: Well, b
is of type Base
, so I'm gonna issue a call to Base::hello()
. And it does exactly that, regardless of the type of the object we actually pass as argument to outputHello
.
To solve this problem and get the output we expected, we need RTTI (Runtime Type Information), whose name speaks for itself. This is achieved by marking Base::hello()
as virtual:
class Base
{
public:
virtual std::string hello() const
{
return "Hello Base!";
}
};
It is also good practice to mark Derived::hello()
as override
, though it is not necessary — override functions are automatically virtual.
class Derived : public Base
{
public:
std::string hello() const override
{
return "Hello Derived!";
}
};
We then get our expected output.
CodePudding user response:
Many Design Patterns (GOF book) rely on virtual functions. The idea is that you have you work with an object of which you only know it's interface and when you call a function, what is done is based on the object that implements the interface.
One of the design patterns is Command where you have a container in which you can add command implementations and the handler of the container calls the "execute" function without having to worry about what the command actually is. The command implementation contains already the data it needs to run.
A common alternative for virtual functions is a switch case that needs to know all the implementations as an enum so the caller knows what to call exactly or an own implementation of a function-table.
Of course, if you don't see how such thing can improve your program, then it's best not to try and force it in.