Virtual Methods in C++

Dr. Lawlor, CS 202, CS, UAF

(See Gaddis Chapter 15 for details on virtual methods)

You can name your methods the same as your parent, and code that knows your type will call your methods.  Unfortunately, other code that only knows about your parent will still call your parent's methods.  For example, "call_doit" here calls the parent's method, not the child's:
class Parent {
public:
void doit(void) { cout<<"I'm doin' it..\n"; }
};
class Child : public Parent {
public:
void doit(void) { cout<<"I'm TOTALLY DOIN' IT, MAN!!!\n"; }
};

void call_doit(Parent &p) {
p.doit();
}

int foo(void) {
Child kid;
kid.doit(); // calls Child::doit
call_doit(kid); // calls Parent::doit
return 0;
}

(Try this in NetRun now!)

The deal with "virtual" is that it makes a child's version of a method callable from a parent pointer:
class Parent {
public:
virtual void doit(void) { cout<<"I'm doin' it..\n"; }
};
class Child : public Parent {
public:
void doit(void) { cout<<"I'm TOTALLY DOIN' IT, MAN!!!\n"; }
};

void call_doit(Parent &p) {
p.doit();
}

int foo(void) {
Child kid;
call_doit(kid); // now that doit is virtual, calls Child::doit
return 0;
}

(Try this in NetRun now!)

Virtual applies to only that one method, and a class can have a mix of virtual and non-virtual methods.

Virtual is forever: "virtual" applies to a grandchild's "doit" method, even if Child's method is not declared virtual.  (I got this wrong back in 2010!)

Here's an example where we're using "virtual" to have old code (the print_payroll function) call new code (all the funky new printer objects).
/***************** original printing code **************/
class Printer {
public:
virtual void print(int x) {
cout<<"original parent Printer print method: "<<x<<"\n";
}
};

void print_payroll(Printer &p) {
p.print(37);
p.print(43);
p.print(17);
}
/******************* years later ****************/
class NonforgablePrinter : public Printer {
public:
void print(int x) {
cout<<"******"<<x<<"******* dollars ("<<x<<" dollars)\n";
}
};

class LoggingPrinter : public Printer {
public:
fstream log;
LoggingPrinter(void) { log.open("log.txt",ios::out); }
void print(int x) {
log<<"Next payroll line: "<<x<<"\n";
}
~LoggingPrinter() {
log<<"-end of log-\n";
log.close();
}
};

class DualHeadPrinter : public Printer {
public:
Printer &a,&b;
DualHeadPrinter(Printer &aNew,Printer &bNew)
:a(aNew), b(bNew) {}

void print(int x) {
a.print(x);
b.print(x);
}
};

int foo(void) {
if (true) {
NonforgablePrinter p;
LoggingPrinter lp;
DualHeadPrinter dh(p,lp);
print_payroll(dh);
} // <- need a close-brace to call LoggingPrinter's destructor before cat...
cat("log.txt");
return 0;
}

(Try this in NetRun now!)


Here's one time when it make sense to reproduce your conceptual taxonomy in C++ objects:
class Object {
public:
// ?
};
class Animal : public Object {
public:
virtual bool happy_in_water(void) { cout<<"You need to overload this\n"; return true; }
virtual string reaction(bool happy) { return "just sits there";}
};
class Otter : public Animal {
public:
bool happy_in_water(void) { return true; }
string reaction(bool happy) {
if (happy) return "claps"; else return "sinks";
}
};
class Cat : public Animal {
public:
bool happy_in_water(void) { return false; }
string reaction(bool happy) {
if (happy) return "purrs"; else return "rips your face off";
}
};

void splash_water(Animal &a) {
bool hap=a.happy_in_water();
if (hap) cout<<"animal is happy\n";
else cout<<"animal is not happy\n";
cout<<"the animal then "<<a.reaction(hap)<<"\n";
}

int foo(void) {
Otter a;
splash_water(a);
return 0;
}

(Try this in NetRun now!)

The problem with taxonomy-as-class-heirarchy is that often inheritance is too strong a property.  For example, say we've got "Shape", "Circle", and "Ellipse" classes in our drawing program.  Everything's a Shape.  But should Circle inherit from Ellipse?  After all, a Circle is a type of Ellipse where the major radius and minor radius are equal.  But Ellipse might have methods like "skew", since a skewed ellipse is still an ellipse; but a skewed Circle isn't a Circle anymore.  So you shouldn't make Circle inherit from Ellipse, or vice versa; instead, just make them both inherit from Shape. 

Generally, deep inheritance hierarchies are a bad idea.