Virtual Functions

Proper utilization?

Page 1 of 1

10 Replies - 1332 Views - Last Post: 06 May 2010 - 05:50 PM Rate Topic: -----

#1 alias120  Icon User is offline

  • The Sum over All Paths
  • member icon

Reputation: 122
  • View blog
  • Posts: 700
  • Joined: 02-March 09

Virtual Functions

Posted 02 May 2010 - 10:13 PM

I have recently begun reading "The C++ Programming Language" and had a question in regards to virtual functions. If I understand the implementation correctly, virtual functions allow derived classes to define functions declared within a base class. My question is when would you prefer to utilize virtual functions and derived classes as opposed to multiple classes with their own functions. I could see this adding a certain level of abstraction to your program, but I am trying to determine other benefits of these functions. What do you think?

-alias

Is This A Good Question/Topic? 0
  • +

Replies To: Virtual Functions

#2 muballitmitte  Icon User is offline

  • D.I.C Regular
  • member icon

Reputation: 174
  • View blog
  • Posts: 470
  • Joined: 05-November 08

Re: Virtual Functions

Posted 02 May 2010 - 11:02 PM

if you use virtual functions and polymorphism you will eventually end-up with a flexible and easily extensible system that it`s able to hide its implementation. Imagine that you have a class and if you want to change a particular aspect of its implementation you will write another one. In such a case you will need to change a huge chunk of the code to fit the change. Now if you had a hierarchy you would only need to expose the base class and leave the rest hidden because any "client" would basically interact through the methods of that class. Also in such a case you could see how easily you could: add new classes to extend the functionality and use classes in ways not known at the beginning. Moreover design patterns use those two concepts.

This post has been edited by muballitmitte: 02 May 2010 - 11:02 PM

Was This Post Helpful? 1
  • +
  • -

#3 alias120  Icon User is offline

  • The Sum over All Paths
  • member icon

Reputation: 122
  • View blog
  • Posts: 700
  • Joined: 02-March 09

Re: Virtual Functions

Posted 03 May 2010 - 03:58 PM

Alright, so by using virtual functions you are actually exposing the Base class while hiding other classes that implement it's functions. So when calling the function from main(), it is in a sense linking the call to the the class implementing the actual funtion? I know that Bjourne said these are typically called polymorphic types, I have only begun learning different aspects of inheritence so I am not too familiar with the concept yet. I appreciate the clarification though, I was initially confused because I thought that this process exposed the classes implementing the functions while hiding the base class. Which of course does not make sense.

-alias
Was This Post Helpful? 0
  • +
  • -

#4 KYA  Icon User is offline

  • g++ jameson.cpp -o beverage
  • member icon

Reputation: 3089
  • View blog
  • Posts: 19,137
  • Joined: 14-September 07

Re: Virtual Functions

Posted 03 May 2010 - 06:10 PM

Perhaps it's poor wording, but I don't agree with the "expose the base class" explanation.


A virtual method (in the context of object/class hierarchy) is to allow the proper function to be called even if the pointer is to the base class. The base class isn't "exposed", unless of course you break the rules of encapsulation.

The concern is design. For example, the usual thing we see [assignment wise] in regards to virtual functions is a bank program. You have a base class Account, of which all other Account types (checking, savings, etc...) derive. You won't know at compile time what various types of accounts the user my play with so you deal with base class pointers casted down and the relationship takes care of the proper function calling. Extensibility is also a plus since all you need to do is add another derived class if you need additional/different functionality while keeping the same "structure" (in regards to methods and whatnot). I need to add a money market account? Inherit Account and write the specific implementation rather then rewriting Account, but only slightly different.


If by "expose" you meant that you deal with handle to derived objects through a base class handle then yes you "exposed" the base class while hiding the derived classes.
Was This Post Helpful? 1
  • +
  • -

#5 alias120  Icon User is offline

  • The Sum over All Paths
  • member icon

Reputation: 122
  • View blog
  • Posts: 700
  • Joined: 02-March 09

Re: Virtual Functions

Posted 03 May 2010 - 09:07 PM

Thank you for the clarification KYA, I can see how my saying "Exposes" the Base class would be mis-leading. I will need to study the subject more in-depth, I already ran into issues trying to overload operators using functions derived from the base class. Practice, practice.

-alias
Was This Post Helpful? 0
  • +
  • -

#6 alias120  Icon User is offline

  • The Sum over All Paths
  • member icon

Reputation: 122
  • View blog
  • Posts: 700
  • Joined: 02-March 09

Re: Virtual Functions

Posted 05 May 2010 - 07:45 PM

I have an additional question. In regards to Interface classes containing pure virtual functions, if i understand correctly you cannot instantiate the base class containing the virtual functions. A derived class must implement the function defined as virtual in the base class. When calling the functions from main(), you must initialize your object to a derived class. The question is how does this hide the implementation of the derived class when the object is initialized to it and the functions called are defined in that derived class? I am trying to see the point of the base class in this situation.

-alias
Was This Post Helpful? 0
  • +
  • -

#7 alias120  Icon User is offline

  • The Sum over All Paths
  • member icon

Reputation: 122
  • View blog
  • Posts: 700
  • Joined: 02-March 09

Re: Virtual Functions

Posted 05 May 2010 - 07:55 PM

Simple example of what I am talking about,

#include<iostream>
#include<string>

using namespace std;


class IBase
{
public:
	
	virtual void setName(string nameIn) = 0;
	
	virtual void setAge(int ageIn) = 0;

	virtual string getName() = 0;

	virtual int getAge() = 0;

};

class Derived : public IBase
{
	string name;
	int age;

public:

	void setName(string nameIn)
	{
		name = nameIn;
	};

	void setAge(int ageIn)
	{
		age = ageIn;
	};

	string getName()
	{
		return name;
	};

	int getAge()
	{
		return age;
	};

};


int main()
{
	Derived obj;

	string usrDefName("Matt");

	int usrDefAge(22);

	obj.setName(usrDefName);

	obj.setAge(usrDefAge);

	cout<<obj.getName()<<endl;
	cout<<endl;
	cout<<obj.getAge()<<endl;

	cin.get();

	return 0;

}


Was This Post Helpful? 0
  • +
  • -

#8 KYA  Icon User is offline

  • g++ jameson.cpp -o beverage
  • member icon

Reputation: 3089
  • View blog
  • Posts: 19,137
  • Joined: 14-September 07

Re: Virtual Functions

Posted 05 May 2010 - 08:52 PM

If it's just virtual (not pure virtual) you can instantiate the base class. If you declare even one method pure virtual, your design is saying "this is really a blueprint".


The usefulness would be more apparent if you had multiple derived classes and a setup like this:

IBase* handle;

//user picks something
handle = new Derived1();

//store it somewhere

//user picks something again or w/e
handle = new Derived2(); 


Was This Post Helpful? 0
  • +
  • -

#9 alias120  Icon User is offline

  • The Sum over All Paths
  • member icon

Reputation: 122
  • View blog
  • Posts: 700
  • Joined: 02-March 09

Re: Virtual Functions

Posted 05 May 2010 - 10:52 PM

View PostKYA, on 05 May 2010 - 07:52 PM, said:

If it's just virtual (not pure virtual) you can instantiate the base class. If you declare even one method pure virtual, your design is saying "this is really a blueprint".


The usefulness would be more apparent if you had multiple derived classes and a setup like this:

IBase* handle;

//user picks something
handle = new Derived1();

//store it somewhere

//user picks something again or w/e
handle = new Derived2(); 



So by creating a pointer to the base class, and then dynamically allocating memory for a seperate function or class you would be allowing the new function/class access to the base classes virtual functions? I apologize for being redundant, but what is the benefit of using abstract types such as these opposed to using concrete types? Is it the fact that dependent on the type of operation the "user" wants to perform they would be able to initialize the same base class function in different ways depending on what they want the outcome to be? In the bank account example you used, if the base class was Account. With virtual functions for check balance, deposit, withdrawl etc. What is the benefit of creating derived classes for each process as opposed to using regular functions under one class? I appreciate your expertise on this issue.
Was This Post Helpful? 0
  • +
  • -

#10 KYA  Icon User is offline

  • g++ jameson.cpp -o beverage
  • member icon

Reputation: 3089
  • View blog
  • Posts: 19,137
  • Joined: 14-September 07

Re: Virtual Functions

Posted 06 May 2010 - 07:22 AM

Here's an example:

Implementation are in matching cpp files combined into one code block for convenience

Base, never instantiated
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include <string>

class Account
{
public:	
	Account()								{};
	virtual ~Account()						{};
	virtual double getBalance()				{ return balance; };
	std::string getDescription()			{ return description; };
	double getAccountMin()					{ return accMin; };
	virtual void retrieveInformation();
	virtual bool Deposit(double) = 0; //pure vitrual must be overridden
	virtual bool Withdrawal(double) = 0; //pure vitrual must be overridden
	virtual void checkWithdrawal(double);
	virtual void calculateInterest();

protected:
	double balance;
	double accMin;
	double interestRate;
	std::string description;
	bool writeChecks;
};
#endif


//implementation
#include "Account.h"
#include <iostream>
#include <iomanip>
using namespace std;

void Account::retrieveInformation()
{
	cout << "\n\t" 				<<										this->getDescription();
	cout << "\n\tCurrent balance:"  	<<								this->getBalance();
	cout << "\n\tMin balance for this type:"	 <<						this->getAccountMin() << endl;
}

//virtual functions that derived classes will override
void Account::checkWithdrawal(double amount)
{
}

void Account::calculateInterest()
{
}




Notice that the pure virtual cannot have an implementation.

Some derived classes:

#ifndef MM_H
#define MM_H
#include "Account.h"

//money market account
class MM : public Account
{
public:
	MM(double);
	~MM();
	bool Deposit(double);
	bool Withdrawal(double);
	void checkWithdrawal(double);
	void calculateInterest();

private:
	//reserved
};
#endif



#ifndef RS_H
#define RS_H

#include "Account.h"
class RS : public Account
{
public:
	RS(double);
	~RS();
	bool Deposit(double);
	bool Withdrawal(double);
	void checkWithdrawal(double);
	void calculateInterest();

private:
	//reserved
};
#endif

#include "MM.h"
#include <iostream>
#include <iomanip>
using namespace std;
extern void clrscr(); //clear screen function

MM::MM(double bal)
{
	balance = bal;
	accMin = 5000;
	interestRate = 0.004167; // (5%/12) calculated on a by month basis
	description = "Money Market";
	writeChecks = false;
}

MM::~MM()
{
	//reserved for later use
}

bool MM::Deposit(double amount)
{
	clrscr();
	if (amount < 150)
	{
		cout << "\n\tMoney Market Deposits must be $150 or greater.\n";
		return false;
	}
	else
	{
		cout << "\n\tCurrent Funds: " <<	this->getBalance();
		balance += amount;
		cout << "\n\tFunds after Deposit: " <<	this->getBalance() <<endl;
		return true;
	}
}

bool MM::Withdrawal(double amount)
{
	clrscr();
	double temp;
	if (amount < 100)
	{
		cout << "\n\tMoney Market Withdrawals must be $100 or greater.\n";
		return false;
	}
	else
	{
		cout << "\n\tCurrent Funds: "  <<	 this->getBalance();
		temp = balance - amount;
		if ((temp) < 5000)
		{
			cout << "\n\tBalance cannot be under $5000.00\n";
		}
		else
		{
			balance -= amount;
			cout << "\n\tFunds after withdrawal (to non MoneyTalks account): "  <<	 this->getBalance() <<endl;
		}
		return true;
	}
}//end withdrawal

void MM::checkWithdrawal(double amount)
{
	clrscr();
	cout << "\n\n\tMoney Market Accounts have no check writing privledges.\n";
}

void MM::calculateInterest()
{
	balance += (balance * interestRate);
}




Only checking accounts have "check writing privileges"

#ifndef NI_H
#define NI_H

#include "Account.h"
class NI : public Account
{
public:
	NI(double);
	~NI();
	bool Deposit(double);
	bool Withdrawal(double);
	void checkWithdrawal(double);
	void calculateInterest();

private:
	//reserved
	int numChecks;
	int checksWritten; //will reset each "month" after calculation of interest
};
#endif

#include "NI.h"
#include <iostream>
#include <iomanip>
using namespace std;
extern void clrscr(); //clear screen function

NI::NI(double bal)
{
	balance = bal;
	accMin = 750;
	interestRate = 0.00;
	description = "No Interest Checking";
	writeChecks = true;
	numChecks = 4;
	checksWritten = 0;
}

NI::~NI()
{
	//reserved for future use
}

bool NI::Deposit(double amount)
{
	clrscr();
	cout << "\n\tCurrent Funds: "  <<	this->getBalance();
	balance += amount;
	cout << "\n\tFunds after Deposit: " <<	this->getBalance();
	return true;
}

bool NI::Withdrawal(double amount)
{
	clrscr();
	if((balance - amount) <= 3)
	{
		cout << "\n\t\tWould cause negative balance. Not valid.";
		return false;
	}
	else
	{
		cout << "\n\tCurrent Funds: " <<	this->getBalance();
		balance -= amount;
		cout << "\n\tFunds after Cash Withdrawl: " <<	this->getBalance() <<endl;
		if(balance < 750)
		{
			cout << "\n\nA $4 fee will be assessed for cash overdraw.\n";
			balance -= 4;
			cout << "\n\tCurrent Funds: "  <<	this->getBalance() <<endl;
		}
		return true;
	}
}

void NI::checkWithdrawal(double amount)
{
	clrscr();
	cout << "\n\tCurrent Funds: "  <<	this->getBalance();
	balance -= amount;
	cout << "\n\tFunds after Check Withdrawl: " <<	this->getBalance();
	checksWritten++;
	if(balance < 750)
	{
		cout << "\nA $25 fee will be assessed for check overdraw.\n";
		balance -= 25;
		cout << "\n\tCurrent Funds: " <<	this->getBalance();
	}
	else if (checksWritten > 4)
	{
		cout << "\n\n\tExceeded monthly 4 check limit. $5 fee assessed.";
		balance -=5;
		cout << "\n\tCurrent Funds: " <<	this->getBalance() <<endl;
	}
	
}

void NI::calculateInterest()
{
	clrscr();
	cout << "\n\tNI Account:\n";
	cout << "\n\n\tNo interest for this type of account.";
	cout << "\n\tMonth has ended. # checks = 4 again.";
	checksWritten = 0;
}




I apologize the code dump, but it would be hard to explain without it. The pure virtual functions in Account indicate that it will never be instantiated. However, it doesn't prohibit us from using a pointer to the base class. The "customer" has a vector of Account pointers:

#ifndef CUSTOMER_H
#define CUSTOMER_H

#include <vector>
#include "Account.h"

class Customer
{
public:
	Customer(std::string&);
	~Customer();
	//Accessor, but void
	void getCurrentAccountInformation();
	//Information Functions
	void displayAllAccounts();
	void createNewAccount();
	void setCurAccount(Account*);
	void setCurAccountNull();
	bool depositNotValid(int, double);
	//ACCESSOR FUNCTIONS
	std::string getCustName()					{ return custName; };
	int getCustID()								{ return custNum; };
	Account* getCertainAccount(int);
	int getnumNI()								{ return numNI;};
	int getnumRI()								{ return numRI;};
	int getnumRS()								{ return numRS;};
	int getnumMM()								{ return numMM;};
	std::vector<Account*> getAccounts()			{ return accounts; };
	Account* getCurAccount()					{ return curAcc; };
	void deleteAccount(int);
	void transferAction();

private:
	int custNum;
	int numNI;
	int numRI;
	int numRS;
	int numMM;
	std::string custName;
	Account* curAcc;
	std::vector<Account*> accounts; //here 
	std::vector<Account*>::iterator iter;
};
#endif




Since a customer can have any number of any type of accounts, storing in any other manner would be unfeasible (we could do stack allocation though). At run time we have this:

Account* temp = 0; //pointer to the base class 
	switch(choice)
	{
		case 1:
			if (cust->getnumNI() == 1)
			{
				cout << "\n\t\tOnly one NI account allowed per customer.";
			}
			else
			{
				temp = new NI(deposit); //cast it to a derived class
				accounts.push_back(temp); //add the pointer to the vector
				curAcc = temp;
				numNI++;
			}
			break;
		case 2:
			if (cust->getnumRI() == 2)
			{
				cout << "\n\t\tOnly 2 RI accounts are allowed per customer.";
			}
			else
			{
				temp = new RI(deposit);
				accounts.push_back(temp);
				curAcc = temp;
				numRI++;
			}
			break;
		case 3:
			if ((cust->getnumRS() == 1) || (cust->getnumRI() == 0))
			{
				cout << "\n\tOnly one RS account per customer.\n\tYou must have an RI account to have an RS.";
			}
			else
			{
				temp = new RS(deposit);
				accounts.push_back(temp);
				curAcc = temp;
				numRS++;
			}
			break;
		case 4:
			if (cust->getnumMM() == 25)
			{
				cout << "\n\t\tOnly 25 MM accounts per customer";
			}
			else
			{
				temp = new MM(deposit);
				accounts.push_back(temp);
				curAcc = temp;
				numMM++;
			}
			break;
		case 5:
			break;
		default:
			break;
	}



Then, we call functions that call various member functions of the account objects. Depending on the relationship it may call the base class function (if there is no corresponding override in the derived class) for example:

void Customer::displayAllAccounts()
{
	for(int i = 0; i < accounts.size(); i++)
	{
		cout << "\nAccount #" << i+1 << endl;
		accounts[i]->retrieveInformation();
	}
	cout << endl;
}



The derived classes don't override retreieveInformation() so the base classes' method gets called regardless of the derived class type:

void Account::retrieveInformation()
{
	cout << "\n\t" 				<<										this->getDescription();
	cout << "\n\tCurrent balance:"  	<<								this->getBalance();
	cout << "\n\tMin balance for this type:"	 <<						this->getAccountMin() << endl;
}




However, I did make it virtual so that, if at any time, I find it would be more functional to delegate this to the derived class(es) all Io have to do is override the function in the derived class.

Each derived account type has different "rulers" for deposits and withdrawals, but since we declared those functions virtual and casted to a derived class we can do the following:

switch (choice)
	{
		case 1:
			cout << "\n\n\tEnter amount to deposit to current account: " ;
			cin.sync();
			cin >> amount;
			cust->getCurAccount()->Deposit(amount); //calls the appropriate derived class function 
			AccountMenu();
			break;
		case 2:
			cout << "\n\n\tEnter amount to withdraw to current account: " ;
			cin.sync();
			cin >> amount;
			cust->getCurAccount()->Withdrawal(amount); //calls the appropriate derived class function 
			AccountMenu();
			break;





TL;DR:

Combining virtual functions and a class hierarchy allows for more flexibility then a single class.
Was This Post Helpful? 2
  • +
  • -

#11 alias120  Icon User is offline

  • The Sum over All Paths
  • member icon

Reputation: 122
  • View blog
  • Posts: 700
  • Joined: 02-March 09

Re: Virtual Functions

Posted 06 May 2010 - 05:50 PM

Wow KYA, thank you very much for the time you took with this. Your example makes the usefullness of abstraction much more apparent. You have also given me much to comb through, so again I thank you. I have to say that the most difficult part of learning to code is understanding conceptual design. You all are very helpful when it comes to this.

-alias
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1