0

I need some explanation on a particular paragraph that I read here.

So in the first example:

// pointers to base class
#include <iostream>
using namespace std;

class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b)
  { width=a; height=b; }
};

class CRectangle: public CPolygon {
public:
int area ()
  { return (width * height); }
};

class CTriangle: public CPolygon {
public:
int area ()
  { return (width * height / 2); }
};

int main () {
CRectangle rect;
CTriangle trgl;
CPolygon * ppoly1 = &rect;
CPolygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << rect.area() << endl;
cout << trgl.area() << endl;
return 0;
}

Why can't I simply refer ppoly1->area() & ppoly2->area() ? Since both those pointers (even if they are of class CPolygon) are pointing to address containing objects of derived class.

Mike DeSimone
  • 41,631
  • 10
  • 72
  • 96
Rohan Dalvi
  • 1,215
  • 1
  • 16
  • 38
  • **Because the language doesn't work that way.** The compiler isn't smart enough to be certain that at that point in execution they will be pointing to things for which `area()` is defined, and besides, that isn't the compiler's job. – Beta Oct 14 '13 at 02:44
  • 4
    Did you read the rest of the tutorial where virtual members are explained? – andy256 Oct 14 '13 at 02:46
  • @andy256 I did read it but I still had this question in my mind. Also, why would I use a pointer of base class to point to members of derived class (by implementing virtual functions) when I can just create an object of derived class and call the area function of it and get my work done? – Rohan Dalvi Oct 14 '13 at 13:05
  • Imagine a method that has to deal with any kind of `CPolygon` to do it's job. We pass it a base class pointer to a derived class object. Our method can then call `area()` without knowing exactly what kind of polygon it is. The virtual method mechanism ensures that the correct `area()` method is called. So now our method can deal with any kind of polygon, *even new sub-classes of `CPolygon` that are created later*. This gives you as the programmer great power. – andy256 Oct 14 '13 at 23:07

2 Answers2

7

Since there's no virtual int area(); declared in CPolygon, there's no linkage for that method to objects of class CPolygon in general.

Basically, you're expecting method lookup to happen at run time (like it's done in Python, JavaScript, and other languages), but in C++ it happens at compile time. The above code implicitly says to the C++ compiler, "CRectangle and CTriangle have methods named area but they have nothing to do with each other nor do they have anything to do with CPolygon."

Remember that the C++ compiler often does not see all the subclasses of a given class at compile time.


why would I use a pointer of base class to point to members of derived class (by implementing virtual functions) when I can just create an object of derived class and call the area function of it and get my work done?

Because it's rare that code knows every last little detail about the objects it works with, and often is has no need to know, so there's simplification to be had.

For example, in this case, if the entirety of your code was one function that created a CTriangle and then called its area method to get the area, print it, and terminate, then yeah, there's not much point. The code is nothing more than a glorified calculator with extra verbage.

But in the real world, you don't have the luxury of everything fitting in one function, or source file. So a piece of code might know that it has a pointer to CPolygon but not what specific subclass. And yet that code needs to know its area. For example, a function that calculates an extruded volume given a cross section polygon and a thickness:

int extruded_volume(CPolygon* poly, int thickness)
{
    return thickness * poly->area();
}

Imagine this function is declared in Extrusion.h and defined in Extrusion.cpp. Further, imagine that each of CPolygon, CRectangle, and CTriangle are separately declared in .h files and defined in .cpp files named after the classes respectively. Then Extrusion.h only needs #include "CPolygon.h" and can go its life without knowing anything about the various subclasses. This works if and only if area is a virtual method.

This also allows subclasses of CPolygon to be added later. For example, a subclass CEllipse representing an ellipse whose "width" and "height" were its minor and major axes lengths, respectively:

class CEllipse: public CPolygon {
public:
int area ()
  { return M_PI * (width/2) * (height/2); }
};

Since we used a virtual method, a CEllipse* could be passed to extruded_volume without changing the latter's code at all.

If area weren't a virtual method, then you would need a separate version of extruded_volume for every possible polygon:

template<typename T>
int extruded_volume(T* poly, int thickness)
{
    return thickness * poly->area();
}

In this case, the code bloat is minor because there's not much code there, but in a larger system this could well be unacceptable. Also note that everything that calls extruded_volume will likely need to be a template as well.

Mike DeSimone
  • 41,631
  • 10
  • 72
  • 96
  • why would I use a pointer of base class to point to members of derived class (by implementing virtual functions) when I can just create an object of derived class and call the area function of it and get my work done? – Rohan Dalvi Oct 14 '13 at 17:21
  • See addenda above. i decided to skip over the "what if you can't use templates and have to do runtime subclass identification" case because it's tl;dr already. – Mike DeSimone Oct 14 '13 at 17:50
0

Because inheritance only works upwards. You can call any function that is defined in a class or defined in that classes parent(s) but you can't call a method in a derived class.

There are two ways to work around this

Cast:

((*CRectangle)ppoly1)->area() //C style
static_cast<*CRectangle>(ppoly1)->area //C++ style 

Virtual Method:

class CPolygon {
 public:
 virtual int area() { return (0); }
 ...
Daniel
  • 1,994
  • 15
  • 36
  • Actually, C++ style should require you to use `dynamic_cast` instead of `static_cast`, and you have to check the result of `dynamic_cast` because it will return a null pointer if the original pointer does not in fact point to the derived type (e.g. it came from a `CTriangle`). The C way throws all caution to the wind and trusts that your pointer is legal. Use `static_cast` to go from a derived class to its base class. Also, the `*` comes after the type name. – Mike DeSimone Oct 14 '13 at 02:59
  • In some code, you'll find `dynamic_cast` used in the form `if(CRectangle* pr = dynamic_cast(ppoly1)) { cout << pr->area() << endl; } else { cerr << "Not a rectangle." << endl; }` though this bothers me because I don't like seeing `=` inside `if(...)` even though it's correct here. – Mike DeSimone Oct 14 '13 at 03:02
  • @MikeDeSimone actually a `static_cast` is quite enough in this case (only because the types are known and statically allocated, not safe in general). Also a C-style cast in C++ is not the same as a `reinterpret_cast` see [this](http://stackoverflow.com/questions/28002/regular-cast-vs-static-cast-vs-dynamic-cast) for reference – PeterT Oct 14 '13 at 03:05
  • I didn't say that a C-style cast worked like a `reinterpret_cast`; it works most like a `static_cast` or it would break a lot of code. Either way, there is no checking to ensure that the cast is actually correct, and you can get undefined behavior if you have a diamond-shaped inheritance graph without using virtual base classes. – Mike DeSimone Oct 14 '13 at 16:11