Jump to content

  • Log In with Google      Sign In   
  • Create Account

Why even use virtual functions in a Parent class?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
11 replies to this topic

#1 cipherous   Members   -  Reputation: 148

Like
0Likes
Like

Posted 05 September 2012 - 12:44 AM

So I am learning about inheritence and currently I am on polymorphism... I got the whole inheriting functions from the parent class in order to not duplicate code however I am stuck on one thing: The virtual function...

So say I have a class named Automobile like so with a child class that is named Ferrari:

[source lang="cpp"]class Automobile{public: virtual void drivespeed();};class Ferrari: public Automobile{public: Ferrari(string name); void drivespeed();private: string mName;};[/source]

[source lang="cpp"]//implementation of classesvoid Automobile::drivespeed(){ //does nothing cout << "UNDEFINED" << endl;}Ferrari::Ferrari(string name) :mName(name){}void Ferrari::drivespeed(){ //drives fast cout << "DRIVES REALLY FAST" << endl;}[/source]



now in the main.cpp file in the main function i could write like so...

[source lang="cpp"]int main(int argc, char **argv){ Automobile *auto = new Ferrari("Spider"); //invoke drivespeed function auto->drivespeed(); return 0;}[/source]

all this does is creates an object of the ferrari type (upcasting it to the automobile type) and then lets me invoke methods of that class... So here is my question:

What difference does the virtual function declaration in the Automobile class make? Couldn't I skip the declaration in automobile function since it is really not even a function (it does nothing) and just declare the function as normal in the Ferrari class? and if i made more child classes of the parent class Automobile, i could do the same?

Please let me know

Thanks,

Wayne Prim

Edited by wayneprim, 05 September 2012 - 12:48 AM.


Sponsor:

#2 cipherous   Members   -  Reputation: 148

Like
0Likes
Like

Posted 05 September 2012 - 12:51 AM

hopefully this isn't so to everyone but in the second block of code it seems to be cut off... ill include it here:

//implementation of classes
void Automobile::drivespeed()
{
//does nothing
cout << "UNDEFINED" << endl;
}
Ferrari::Ferrari(string name)
:mName(name)
{
}
void Ferrari::drivespeed()
{
//drives fast
cout << "DRIVES REALLY FAST" << endl;
}

#3 Rattenhirn   Crossbones+   -  Reputation: 1752

Like
9Likes
Like

Posted 05 September 2012 - 01:12 AM

What difference does the virtual function declaration in the Automobile class make? Couldn't I skip the declaration in automobile function since it is really not even a function (it does nothing) and just declare the function as normal in the Ferrari class? and if i made more child classes of the parent class Automobile, i could do the same?


Well, you could've just tried it out!

Anyway, here's the solution:
If you do not have a "drivespeed" function in "Automobile", your main would not compile.
If you'd make it not virtual, then your main would call the "drivespeed" function of the "Automobile" class and not of "Ferrari".
And that's exactly what polymorphism is. You can use a pointer to the base class to call a virtual function and it will automatically call the function implementation that matches the actual class.

Additionally, you state that the "drivespeed" function doesn't really make sense, because the speed of an "Automobile" is unknown. This can also be expressed in C++ by making the function "pure virtual".

That would look like this:
class Automobile
{
public:
       virtual void drivespeed() = 0; // no implementation needed here
};

And lastly, the purpose of inheritance is not to avoid code duplication, but to semantically link classes with similar functionality.

I hope that helps!

#4 cipherous   Members   -  Reputation: 148

Like
0Likes
Like

Posted 05 September 2012 - 01:32 AM

This does help, thanks!! I just feel like making that virtual declaration in Automobile is so useless still, because if I wanted I could just erase it, and then in the main.cpp just call

Ferrari auto("NAME");
auto.drivespeed();

maybe ill get it more when there are more complex functions but it just seems like a useless addition... lol sorry if I am not getting this... the answer is probably very simple and will be something like, "Ohhhh, wow I see it now.." :P

#5 rnlf   Members   -  Reputation: 1123

Like
3Likes
Like

Posted 05 September 2012 - 01:52 AM

It is not useful in this case. But consider this:

Auto* ferrrari = new Ferrari;
Auto* lambo = new Lamborghini;

do_something_with_a_car(lambo);

void do_something_with_a_car(Auto* auto) {
	 auto->drivespeed(); // which one?
}

Using your approach you would need one overload of do_something for every car type. But you want to use the same function so you don't need to copy and paste the same code over and over again, just changing the argument type. Or image you want to store a list of cars where you don't know in advance what type of car it will be. You will be using a list of Auto*, where each Auto* can point to a different subclass of Auto (Lamborghini, Ferrari, Fiat, Maserati, Alpha Romeo, Lancia, ...).

You *will* see the use of it once you need it ;-) This is all intended for more complex programs, so in small examples, these language features (like polymorphism) may sometimes seem a bit odd...

my blog (German)


#6 samoth   Crossbones+   -  Reputation: 4783

Like
1Likes
Like

Posted 05 September 2012 - 02:31 AM

Imagine this is not about a function drive_speed but about a function ignition_on. Every car needs to call ignition_on() before it can drive, and it does the same thing. Why write the code for every type of car if you can have it in the parent class?

Oh, and then there's electric cars which don't have ignition... this is where you override it to do nothing. In the future, there may be cars that are propelled by blowing up a big explosive charge on the rear. In this case, ignition_on will be overloaded to trigger the explosive.

Edited by samoth, 05 September 2012 - 02:33 AM.


#7 rnlf   Members   -  Reputation: 1123

Like
0Likes
Like

Posted 05 September 2012 - 05:10 AM

samoth, your example of electric cars is a typical case of interface bloat. If not all cars need ignition_on, don't put it into the base class. But it's okay as an explaination of virtual functions, I guess ;-)

Edited by rnlf, 05 September 2012 - 05:11 AM.

my blog (German)


#8 Rattenhirn   Crossbones+   -  Reputation: 1752

Like
0Likes
Like

Posted 05 September 2012 - 08:28 AM

Electric cars don't have an ignition?

#9 BCullis   Crossbones+   -  Reputation: 1813

Like
1Likes
Like

Posted 05 September 2012 - 08:54 AM

They have a "power on" button, but ignition connotes lighting something on fire (igniting), i.e. sparking the fuel in the cylinders.
Electric cars just have an on switch.

OP: another example: let's say you have an object Garage that keeps an array of Automobile pointers (Automobile* cars[20]). You can go through and new those pointers into any kind of class that derives from automobile. But with the virtual function drivespeed() in the base Automobile class, you could loop through the entire array and call

for(int i = 0; i < 20; i++)
{
	 cars[i]->drivespeed();
}

without having to know what kind of subclass they even were, and knowing full well that each subclass instance would run their version of the base method appropriately.
Hazard Pay :: FPS/RTS in SharpDX
DeviantArt :: Because right-brain needs love too

#10 Servant of the Lord   Crossbones+   -  Reputation: 19556

Like
4Likes
Like

Posted 05 September 2012 - 09:08 AM

(fyi: If you try to compile rnlf's example, be warned that 'auto' is a reserved keyword and you may get a few strange error messages)

@OP: If I have a class 'Car', really any brand or model of car should just be created by passing data into Car to define the car, instead of creating a new class.
class Car
{
	 public:
	 Car(unsigned int milesPerGallon, unsigned int topSpeed, std::string manufacturer, std::string model, std::string color);

};

Car ferrari(20, 120, "Ferrari", "458 Italia", "Red");
Car lamborghini(35, 110, "Lamborghini", "Aventador", "Sky blue");

To specify or define an instance of a class, I should pass data into a single class (e.g. 'Car').
Or, if I frequently need to create classes with most the data filled (like cars of a Ferri type), I can create a helper function outside of the class, or a static function, to take car of it for me:
Car MakeFerrari(unsigned int milesPerGallon, unsigned int topSpeed, std::string model, std::string color)
{
	 return Car(milesPerGallon, topSpeed, "Ferrari", model, color);
}

Car myRedItalia = MakeFerrari(20, 120, "458 Italia", "Red");
And if I am defining default values, this can be done with additional constructor overloads or default parameters.
Car(unsigned int milesPerGallon, unsigned int topSpeed, std::string manufacturer, std::string model, std::string color = "white");
Car(unsigned int milesPerGallon, unsigned int topSpeed, std::string manufacturer);

To have and use the functionality of one class inside another class, I can make Class B have a Class A as a member variable (Called 'Composition').
class NascarDriver
{
	 private:
	 Car car;
	 Driver driver;
	 Point position;

	 int carNumber;
	 int currentLap;
}

To extend​ the functionality of a class, I can inherit it in a new class.
class CarExtended : public Car
{
	 //Additional stuff that 'Car' doesn't have on it's own.
}

To extend AND alter the functionality of a class, you can inherit the class and overload some of the original class's functions.

class Car
{
	 void ShiftGear(gear);
	 void DriveAt(int speed)
	 {
		  if(IsWithinGearLimit(speed))
		  {
			   speed = currentGearSpeedLimit;
		  }

		  SetCurrentSpeed(speed);
	 }
};

class Automatic : public Car
{
	 //Overloaded function.
	 void DriveAt(int speed)
	 {
		  if(IsWithinGearLimit(speed))
		  {
			   //Figure out the best gear for this speed.
			   Gear gear = GetBestGearFor(speed);
			  
			   Car::ShiftGear(gear);
		  }
		  
		  //Call the original non-overloaded inherited function, now that we've changed gears automaticly.

		  Car::DriveAt(speed);
	 }
};

To extend and alter the functionality of a class, AND pretend it's actually the original class, you can use virtual functions.
class Car
{
	 virtual void DriveAt(int speed);
};

class Automatic
{
	 void DriveAt(int speed) override; /* 'override' keyword is new in C++11. Use it if you got it. */
};

void DriveCar(Car *car)
{
	 car->DriveAt(55); //Unless 'DriveAt()' is virtual, this will always call Car::DriveAt() and not Automatic::DriveAt().
}

Automatic myAutomaticCar;
DriveCar(&myAutomaticCar);
'override' and 'final' keywords (C++11)

One of the best things about virtual functions, is you can treat each sub-class as the base class, but still have custom functionality.
enum MovementCapabillities {CanFlyInAir, CanHoverInAir, CanSwimUnderwater, CanFloatOnWater, CanDriveOnGround};
class Vehicle
{
	 Vehicle(std::string manufacturer, std::string model, std::string color, unsigned int flags);

	 //By assigning a virtual function to null, you are saying "I'm not going to implement this in the base class, and
	 //I want the compiler to refuse to compile if someone tries to use the base class directly instead of inheriting it.
	 //
	 //nullptr is C++11, use NULL or 0 if you don't have it. But use nullptr if you do have it.
	 virtual void MoveToLocation(Point location) = nullptr;
};

class Airplane
{
	 AirPlane(int cruisingAltitude, int wingspan, std::string callsign, std::string manufacturer, std::string model, std::string color) :
    	  Vehicle(manufacturer, model, color, CanFlyInAir);

	 void MoveToLocation(Point location) override;
};

class Boat
{
	 Boat(int boatLength, std::string boatName, std::string manufacturer, std::string model, std::string color) :
		  Vehicle(manufacturer, model, color, CanFloatOnWater);
	 void MoveToLocation(Point location) override;
};

class Helicopter
{
	 Helicopter(std::string manufacturer, std::string model, std::string color) :
		  Vehicle(manufacturer, model, color, CanHoverInAir);
	 void MoveToLocation(Point location) override;
};

class TiltRotorAircraft
{
	 TiltRotorAircraft(std::string manufacturer, std::string model, std::string color) :
		  Vehicle(manufacturer, model, color, CanHoverInAir | CanFlyInAir);
	 void MoveToLocation(Point location) override;
};

class Hovercraft
{
	 Hovercraft(int width, std::string manufacturer, std::string model, std::string color) :
		  Vehicle(manufacturer, model, color, CanFloatOnWater | CanDriveOnGround);
	 void MoveToLocation(Point location) override;
};

class Automobile
{
	 Automobile(std::string manufacturer, std::string model, std::string color) :
		  Vehicle(manufacturer, model, color, CanDriveOnGround);
	 void MoveToLocation(Point location) override;
};

class Submarine
{
	 Submarine(std::string manufacturer, std::string model, std::string color) :
		  Vehicle(manufacturer, model, color, CanSwimUnderwater);
	 void MoveToLocation(Point location) override;
};

Then you can go like this:
std::vector<Vehicle*> vehicles;
vehicles.push_back(new AirPlane(10000, 120, "Red Leader", "Rebel Forces, Ltd", "X-Fighter", "Dusty White"));
vehicles.push_back(new Boat(500, "Rosabelle", "Cox & King", "Yacht", "White"));
vehicles.push_back(new Helicopter("Sikorsky Aircraft", "S-67 Blackhawk", "Jet Black"));
vehicles.push_back(new TiltRotorAircraft("Bell Boeing", "V-22 Osprey", "Army Green Camo"));
vehicles.push_back(new Hovercraft(27, " British Hovercraft Corporation", "SR.N6", "Red"));
vehicles.push_back(new Automobile("Honda", "Civic", "Black"));
vehicles.push_back(new Automobile("Piaggio", "Vespa LXV 125", "Forest Green"));
vehicles.push_back(new Submarine("GDEB", "Seawolf class", "Battleship Grey"));

Point targetLocation;

//Move zigs, move! For great justice!
for(auto vehicle : vehicles)
{
     //Each subclass will use it's own overloaded virtual function to do the actual moving, so each subclass
     //can decide the best way to get to the destination, using it's own limitations and strengths.
     vehicle->MoveToLocation(targetLocation);
}
(That vector should really be a vector of shared_ptr or unique_ptr (C++11), otherwise you'll have to make sure to free the memory manually. The special for-loop construct is a C++11 feature as well)
It's perfectly fine to abbreviate my username to 'Servant' rather than copy+pasting it all the time.
All glory be to the Man at the right hand... On David's throne the King will reign, and the Government will rest upon His shoulders. All the earth will see the salvation of God.
Of Stranger Flames - [indie turn-based rpg set in a para-historical French colony] | Indie RPG development journal

[Fly with me on Twitter] [Google+] [My broken website]

[Need web hosting? I personally like A Small Orange]


#11 cipherous   Members   -  Reputation: 148

Like
0Likes
Like

Posted 05 September 2012 - 02:22 PM

thanks for all the help, I got it now! lol :)

#12 Shaquil   Members   -  Reputation: 819

Like
0Likes
Like

Posted 05 September 2012 - 02:52 PM


What difference does the virtual function declaration in the Automobile class make? Couldn't I skip the declaration in automobile function since it is really not even a function (it does nothing) and just declare the function as normal in the Ferrari class? and if i made more child classes of the parent class Automobile, i could do the same?


Well, you could've just tried it out!

Anyway, here's the solution:
If you do not have a "drivespeed" function in "Automobile", your main would not compile.
If you'd make it not virtual, then your main would call the "drivespeed" function of the "Automobile" class and not of "Ferrari".
And that's exactly what polymorphism is. You can use a pointer to the base class to call a virtual function and it will automatically call the function implementation that matches the actual class.

Additionally, you state that the "drivespeed" function doesn't really make sense, because the speed of an "Automobile" is unknown. This can also be expressed in C++ by making the function "pure virtual".


Perfect explanation. Thanks a lot




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS