9/27/2015
As we saw earlier, the main goals of C++ are as follows:
This means that when you have to make methods that accept
general types, you have to make it fast and flexible.
Java tries to do general types with interfaces and generic
types. This works well, but incurs a big runtime
overhead. Let's do it anyway, because you're going to see
it somewhere eventually. This concept of making
a interface
then extending it is called
polymorphism. Polymorphism is a very easy way to create
abstractions but it is pretty slow. Here is how it works
in C++. Next post I'll look at template metaprogramming
which is like generic programming in Java on steroids.
class printable { public: virtual void print() = 0; }; class one : public printable { public: void print() { std::cout << "one" << std::end; } }; class two : public printable { public: void print() { std::cout << "two" << std::end; } };
Here we see that we use a new keyword,
virtual
and some weird = 0
thing. virtual
tells the compiler that this
class should be extensible and overridden print methods
should be called instead printable
's own.
This is the default behavior in Java but it isn't in C++ for performance reasons. If a class isn't polymorphic, not having any virtual methods, then it can be turned into a plain old data structure and all calls to its "methods" are just function calls that take an implicit pointer to the struct. This means that using object orientation without inheritance is as fast as C!
The = 0
is the exact same as the abstract
keyword. It just enforces that the printable
"interface" must have an overridden print
method to be able to be constructed.
One thing you should note is that you use a colon to show
extension, rather than a keyword. The colon takes a list
of classes and the way in which to extend them. By
default, inheritance is private
. This means
that only your class knows that it is an instance of the
other class. Most of the time private inheritance is used
when you want to do advanced template metaprogramming and
public inheritance is done most everywhere else.
Protected inheritance is the other modifier, meaning that
only subclasses have access to the data or to the knowledge of inheritance, as there are no
packages in C++.
Now let's look at an example of subtle problems with
polymorphism in C++. Java solves this by making
everything a pointer, and the behavior of a pointer in C++
is the same intuitive thing that it is in Java. BTW
putting braces in C++ enters a "sub scope". It's like
an if(true) {}
.
int main() { // Error! Can't use a abstract class as value type! // printable x = sub(); // Error! Can't point a reference to an unnamed value! // printable& x = sub(); one o; two t; { printable& x = o; x.print(); // output one } { printable& x = t; x.print(); // output two } { printable& x = t; x = o; x.print(); // ouput two! Remember that a reference is a pointer to one // unchangable memory location. Assigning to it will corrupt // ``t`` rather than changing the memory location! } { printable* x = &t; x = &o; x->print(); // output one! Assigning to a pointer will change the pointer // rather than the memory! } }
In the main method, we see that an interface / abstract class is best used as a pointer, which takes away the benefits of deterministic destruction at the end of scopes.
The problem with polymorphism is that it forces the entire class to be allocated differently and that method call to be looked up at runtime. Template Metaprogramming shifts this method lookup to compile time and makes your methods more general by removing interfaces and just accessing fields directly. This can even make your methods generically work over objects and native types (int, float...) all in the same way!