Computer Science Canada [WIP] C++ OOP Whirlwind |
Author: | wtd [ Thu Oct 26, 2006 5:27 pm ] | ||||||||||||
Post subject: | [WIP] C++ OOP Whirlwind | ||||||||||||
Let's create a class!
This class does precisely nothing useful. However, what it does do is create a new type. We can declare a variable of this type.
We can dynamically allocate memory for that type.
We can delete it.
We can write a function which takes such a value as a parameter.
We can use that type in overloading existing functions.
|
Author: | wtd [ Thu Oct 26, 2006 5:28 pm ] | ||||||||||||||||
Post subject: | |||||||||||||||||
Member functions!
So now we can just create a Foo object and call that method, right?
Not so fast. By default, everything in a class is private. We'll have to explicitly make that member fucntion public.
Now the code we'd written previously works. Or, at least it will if we actually define the member function we declared. Otherwise, you'll get a linker error when the linker goes looking for the member function that isn't actually defined.
Now, let's try calling that again.
Oops! The variable "f" hasn't yet been initialized.
Dodged a bullet there. No one likes dealing with segmentation faults. Only one problem remaining.
Now, we have a constant Foo object. Really, this makes little sense, as at this point all Foo objects are basically constant. However, this is a big C++ quirk, and one worth understanding. To make bar work in this case, we'd have to qualify it as being ok to call on a const object.
|
Author: | wtd [ Thu Oct 26, 2006 5:29 pm ] | ||
Post subject: | |||
State
So far all Foo objects have been boring because they're all exactly the same. To differentiate them, each object needs to hold some internal state that can be different from other Foo objects. Now, in the above code sample, each Foo object will now have a member variable called "_bar." There's just one problem. Since everything in a class is private unless otherwise specified, there's no way to set the value of that variable. |
Author: | wtd [ Thu Oct 26, 2006 5:29 pm ] | ||||||||||||||
Post subject: | |||||||||||||||
Construction
Constructors provide a way to initialize the state of an object. We may now write the following.
And the output will be as follows.
We may have more than one constructor, following C++ function overloading rules. Though constructors are not actually functions.
Here we've created a default constructor. Such a constructor will be called in either of the following situations.
It's important to note that we might also accomplish the above effect using default parameter values.
However, we cannot achieve the following effect using default values.
This is known as a copy constructor. It literally copies the state of another object into the new object. |
Author: | wtd [ Thu Oct 26, 2006 5:31 pm ] | ||||||||||||
Post subject: | |||||||||||||
Being explicit
Previously our constructors have not been qualified as explicit. Without this, we could have had code of the sort that follows.
With the explicit qualifier, we now require one of the following.
But this also makes it impossible to write the following.
Instead we would have to:
Instead, perhaps we should :
|
Author: | wtd [ Thu Oct 26, 2006 5:31 pm ] | ||
Post subject: | |||
Assignment
Previously we showed off assignment-style construction by leaving the copy constructor non-explicit. This only applies to construction. Once the object is constructed, other requirements come into play. Then we need to overload the assignment operator, as has been done above. Since this changes the state of the object, it is naturally not qualified by "const." |
Author: | wtd [ Thu Oct 26, 2006 5:32 pm ] | ||
Post subject: | |||
A few more operators
It's quite common, and highly useful to overload various operator for objects. A few of the more common ones are the equality and the less than operator. This permits various functions in the standard library to determine proper ordering of collections of objects. |
Author: | md [ Thu Oct 26, 2006 9:55 pm ] |
Post subject: | |
Very nice wtd, I didn't know about explicit before... /me should read the ISO C++ standards. |
Author: | wtd [ Fri Oct 27, 2006 4:15 pm ] | ||||||||||||||
Post subject: | |||||||||||||||
Friends
A lot of people write code like this. Don't. This doesn't offer any way to choose the output stream to print to. But that's simple, they think.
The problem with this is that it till runs counter to how we output any other value in C++. Those values are simply passed to the insertion operator, along with the desired output stream. Then the output stream is returned, so it can be used again. So let's implement such a thing.
But... imagine now that the "bar" method didn't exist. We'd have no way of getting at the state of the Foo object. However, if we made this operator a friend of the Foo class...
Now we can easily:
Just as easily, input can be handled.
|
Author: | wtd [ Fri Oct 27, 2006 4:37 pm ] | ||||||||||
Post subject: | |||||||||||
Simple inheritance
Nothing about Foo has changed at all from our previous examples. But now we have a subclass of Foo called Bar. Bar is pretty boring, but it does gain all of the capabilities of Foo. Well, all except for Foo's constructors. And the private sections of the Foo class are inaccessible in the Bar class. So... let's add some constructors to Bar.
And then we'll implement them.
I've called Foo's constructors from Bar's constructors. This allows me to initialize the "_bar" instance variable that is otherwise inaccessible save through the "bar" member function. The is-a relationship The is-a relationship is tightly tied to inheritance. By creating Bar as a public subclass of Foo, I have established the relationship that a Bar object is a Foo object. Thus, it's possible to write and of the following.
But I cannot:
While a Bar is a Foo, a Foo is not a Bar. |
Author: | wtd [ Fri Oct 27, 2006 5:23 pm ] | ||||||||||||||||||
Post subject: | |||||||||||||||||||
Overriding
So here we have two very simple classes. B is a subclass of A, and overrides the "id" member function in A. Let's implement them.
So, now if we write:
We'll get as output:
That's pretty straightforward, but remember that stuff about is-a? Well, let's see how that changes things.
This works perfectly well. B is a A, so we can assign a value of type B to a variable of type A. And the output is:
So, why did this happen? Well, As far as the program is concerned, the variable "b" is of type A. Virtual methods
The key to this quandry is to make the member functions virtual. Without this, all of the function linking is done at compile-time. Calling "id" on any variable typed as "A" will always call the base classes' "id" member function. With virtual, this linking is held off until run-time, and then the best candidate is chosen. This only works with pointers, though, as in the following.
Which results in:
|
Author: | wtd [ Sat Oct 28, 2006 12:32 pm ] | ||
Post subject: | |||
Augmenting
Previously, our examples have demonstrated inheritance, but the subclass has never gotten functionality beyond that of the base class. In the example above, though, HairyAnimal has member variables and functions lacking in Animal. There's nothing especially remarkable about this, but it does mean that a HairyAnimal object assigned to anvariable typed as Animal will only allow the memember functions defined in Animal to be called. |
Author: | wtd [ Sat Oct 28, 2006 4:36 pm ] | ||||||
Post subject: | |||||||
Interface inheritance
So, what's going on here?
HairyThing is a purely virtual class. It has no non-virtual member functions, and they're all initialized to zero. This means they cannot have any real definition. As a result, it is impossible to actually create a HairyThing object. So why the heck did I create this class? This class serves as an "interface." Any class which inherits from it is forced to implement the members specified in HairyThing. Of course, that says nothing about how those member functions are actually implemented. The neat thing about this is that it links together classes simply by what they can do.
Both of the above are perfectly legal. |