Classes (Not the school ones)

Started by
9 comments, last by bxela1 16 years, 11 months ago
I'm currently trying to grasp the concept of classes and finding it very difficult. Does anyone know of a tutorial or anything that explains classes. Anything is better than what I have.
##########################################################You'll have time to rest when you're dead - Robert De Niro
Advertisement
First, forget everything you know about classes. The following will be easier to grasp if you don't spend your time wondering how it fits with what you already know.

Objects



Object-oriented programming, as the name implies, relies on objects to solve problems. An object could be likened to a cogwheel in a wrist watch: it is a cohesive element which interacts with other elements of the system, and the combination of all objects in the system ultimately performs the task for which it was designed.

So, what's an object? It has three different properties. First, an outside—the interface—which allows it to interact with other objects in the system. A car engine has a fuel intake and generates motion on its axle. Second, a principle—the implementation—which describes how the object performs whatever tasks it has to do. A car engine has explosion chambers to detonate the air-fuel mix, with pistons to transmit motion to the axle. Thirs, an inside—the state—which is how the object currently looks like under-the-hood. A car engine may be in a "two pistons up, two pistons down" state, or perhaps in a "four pistons midway" state. As the object functions, its state changes (the interface and implementation don't).

In the computer science world, a simple example of an object would be a stack of integers. A stack of integers has an interface: it allows the program to push integers on top of the stack, and to pop the top integer out. It also has an implementation: for instance perhaps the stack is implemented as a singly linked list, which wires the top element to the rest. Finally, it obviously has a state: the current contents of the stack, from top to bottom, fully represent what the object is at any given time. Here is a quick example in Objective Caml. Implementation is in red, interface in green, and state only exists at runtime (it is not described in code).

let stack = object  val mutable contents = []  method push (i:int) =     contents <- i :: contents  method pop =     match contents with    | [] -> failwith "Empty stack"    | h::t -> contents <- t; hend;;


I have chosen Objective Caml to illustrate the point that only the interface is relevant to an outside observer: unless you know how ML lists work, you probably don't understand the red code. However, you do understand the green interface, which is exactly what external code would use, without caring about how the interface is implemented. Note that Objective Caml answers:

val stack : < pop : int; push : int -> unit > = <obj>


Which means that stack is an object (the implementation of which is hidden), and which implements a pop-and-push interface. In case you're wondering (although this is not relevant yet), the stack object above does not have a class.

Objects are interacted with through means of messages. The implementation of message-passing varies from language to language, although most implement it as methods. Above, push is a method. To push the integer 10 on the stack, we would write:
stack # push 10;;


Interfaces



A first common observation is that a large amount of objects have the same interface. All locks work the same way: insert the key into the keyhole and turn, though locks accept different keys—implementation— and may be either open or closed at any given moment—state. Moreover, only the interface is important to the users anyway! This constatation led to the creation of interfaces, which most languages support: interface keyword in C# and Java, abstract classes in C++, abstract classes and anonymous interfaces in Objective Caml, and so on. An interface in a programming language is in fact a description of how a given object can be used: if an object can be used through a certain interface, then it is said to implement that interface. Let's do a quick illustration in O'Caml:
let get s = s # pop + 1;;

To which O'Caml answers:
get : < pop : int; .. > -> int = <fun>


Here, O'Caml has automatically generated an anonymous interface, < pop : int; .. >, which describes that the object on which the get function is called must have a pop method which returns an integer. This lets us call get on stack, because stack does have such a function, but any other similar object would work as well.

The other option is explicitly describing the interface. Here is an example from java for that same object above:
public interface IntStack {  public int pop();  public void push(int i);}


Classes



A second common remark is that many objects share the same implementation. For instance, two different integer stacks will probably have different states, but identical implementations. Just like similar car engines are created identically from the same initial plan, so can objects be created identically from the same template. Such templates are almost universally called classes, and objects which are created from a class are called instances of that class. A class specifies the full interface and the full implementation of its instances, and also provides one or more possible initial states for the instances. We could, for instance, turn our aforementioned Objective Caml stack object into a class for generating such objects:
class stack_class = object  val mutable contents = []  method push (i:int) =    contents <- i :: contents  method pop =    match contents with    | [] -> failwith "Empty stack"    | h::t -> contents <- t; hend;;let stack_instance_1 = new stack_class;;let stack_instance_2 = new stack_class;;


To which O'Caml answers:
class stack_class :  object    val mutable contents : int list    method pop : int    method push : int -> unit  endstack_instance_1 : stack_class = <obj>stack_instance_2 : stack_class = <obj>


Note that the two instances we created are distinct: their interface and implementation is identical, but their states will vary independently of each other. The same principle is used in all other languages: C++, C#, Java, Python, PHP etc. You define a class and then create instances of it using new (except in the case of C++, where instances may be created as auto, temporary and global variables without new). Note that in many languages (C++, C#, Java) objects can only be created from a class, while other languages allow class-less objects too.

Regardless of the language, a class description always contains the same elements:
  • (Underlined) The name of the class.
  • (italics) An optional list of any interfaces the class may implement (or inherit from, but I will get to that topic later).
  • (Green) An optional list of member functions (methods) that are part of the interface of the object.
  • (Red) An optional list of member functions (methods) that are part of the implementation of the object, along with the code of any interface methods, as well as a list of member variables that describe the state of the object.
  • (Blue) A way of obtaining the initial state of the object (typically, one or more constructors). Some languages provide default constructors if none is given.


In several languages:

// O'Camlclass classname = object  inherit someinterface  val membervariable = initialvalue  private method implementation_method = 0  method interface_method = 1+1end// C++class classname : public someinterface {  int membervariable;  virtual int implementation_method() { return 0; }public:  classname() : membervariable(initialvalue) {}  virtual int interface_method()     { return 1 + 1; }};// Javapublic class classname implements someinterface{  private int membervariable;  private int implementation_method() { return 0; }  public classname()     { membervariable = initialvalue; }  public int interface_method()     { return 1 + 1; }}// PHP (no language-enforced implementation-interface separation)class classname extends someinterface{  var $membervariable;  function implementation_method() { return 0; }  function classname()     { $this->membervariable = initialvalue; }  public int interface_method()     { return 1 + 1; }  }


this, self



You might have noticed in the example above that the PHP code used a $this variable. This is because an object might want to access its own methods and variables. As such, most languages provide an access to a this variable, which represents the object of which the method was called. The details vary from language to language:

  • In C++, C# and Java, the this object is inferred if absent. If an object method calls othermethod(), then the compiler will automatically replace it with this->othermethod() or this.othermethod(), as if you had written it. Some people prefer to write the this themselves, to indicate to other programmers (or themselves) that the code is accessing a member function. In PHP, the $this-> prefix is mandatory for all member access. In O'Caml, the prefix is mandatory for methods, but is neither required nor usable for variables.
  • In O'Caml, one can choose the name of the this reference. The idiom is to call it self. The name is defined between parenthesis following the object's name:
    object (self) =  method facto n =     if n = 1 then 1 else n * self # facto (n-1) end

  • In C++, this is a pointer. In all the other languages mentioned here, it's a reference.
.

Inheritance



We have our stack. We now wish to have a stack which counts the number of objects inside it. We want: 1° to reuse the code we wrote for the original stack class, and 2° to be able to use our new stack class in code (such as get) which was designed for our initial class. The tool used here is called inheritance. The idea is to extend an existing class of object (the base class) into a new class (the derived class). The derived class is an extension: instances created from it will contain an imaginary instance of the base class. Such instances also provide the same interface as the base instances (plus additional stuff if applicable), which means that the derived instances can be used wherever an instance of the base class could be. Without further ado, here is our new stack class:

class count_stack = object   inherit stack as base  val mutable count = 0  method push (i:int) =    count <- count + 1;     base # push i  method pop =    let popped = base # pop in    count <- count - 1;    popped  method count = countend


Here, the imaginary base instance inside any derived instance has been named base (again, it's because O'Caml allows naming your self and base references, other languages don't). We can clearly see the push and pop messages being manually forwarded to the base instance (thus reusing the implementation of that object). The interface still provides push and pop, so we could use instances of this class wherever an instance of base could be used.

To make inheritance simpler to use, most languages don't even require that you implement all the methods that exist in base instances: if one of these methods isn't provided, the language automatically forwards the call to the base instance. In essence:

class base = object method x = 1 end;;class derived = inherit base end;;(new derived) # x;;


The example above calls the x method of a derived object. The call is forwarded to the imaginary base instance, which returns 1.

Another important detail is that the imaginary base instance inside the derived instance has no existence of its own: it is only is present through the derived instance. There is still only one instance, which is of the derived class, and which can act as a base instance if need be. Because of this, self or this in a base instance always refers to the entire object. If the entire object is a derived instance, then the self will be a reference to the entire derived instance, not the base portion of it:

class base = object (self)  method x = self # y  method y = "base"end;;class derived = object  inherit base  method y = "derived"end;;(new derived) # x;;


The above example would return "derived", because self # y in the base instance would send the y message to the entire (derived) instance, not only the base part of it.

In short, inheritance allows the programmer to use instances of a class (the derived class) as if they were instances of another (base class), and also providing access to the functionality of the base instances from the implementations of the derived instances.

Forwarding a call to the base instance depends on the language. In PHP, it's impossible as far as I know, because the derived function definition effectively overwrites the base one. In C++, it's based on prefixing the name with the correct qualifier (this->Base::y()). Java and C# have their own, which I don't remember exactly right now, but at least one of them relies on a base keyword.

The virtual keyword in C++ and C#



Let's now consider the above O'Caml examples in another language. Specifically, in C++:

class base {public:  std::string y() const { return "base"; }};class derived : public base {public:  std::string y() const { return "derived"; }};base * b = new derived;b->y();


This code would generate "base". Why? Because the default behaviour of member functions in C++ is not the same as in O'Caml. Member functions in C++ are not bound to the object, they are bound to the variable (that's not technically true, they're bound to the expression where the call is made, but it's usualy a variable, so bear with me). Here, your variable is of type base* (it's a pointer to an instance of that class), so the program will use the definition of y found in the base class, even though the object itself is an instance of derived. The exact same thing happens in C#.

To retrieve the usual behaviour of the functions (that is, to bind the functions to the object, instead of the variable), you have to use the virtual keyword. This keyword performs the correct binding, which allows the correct version of the function to be used depending on the type of the object.

class base {public:  virtual std::string y() const { return "base"; }};class derived : public base {public:  virtual std::string y() const { return "derived"; }};base * b = new derived;b->y();


Note that in C++, the virtual keyword in the derived class is not necessary (because the binding is performed in the base class). In C#, the derived class must use the override keyword instead, to indicate that you are matching an existing virtual function instead of creating a new one.

In Java and PHP, there is no need for the virtual keyword, since functions are bound to instances instead of variables by default.



This is about all you need to get going with classes. More advanced subjects (multiple inheritance, virtual inheritance, instance slicing, virtual destructors, pointers-to-members, C++ name lookup, and so on) will generally appear only much later. For now, just make sure you understand the above, and don't confuse instances and classes.
Excellent post, ToohrVyk!

To the OP, I would only add the following: You said "anything is better than what I have," so how about telling what you have currently? What reference are you using to learn (I assume) C++? There are online books ("Thinking in C++" and "C++: A Dialog") that are freely available -- Google them -- that do a good job of explaining the C++-specific syntactical machinations required of you, and ToohrVyk's post covers some critical theoretical aspects quite well, so between those three you should be all set.
Ok i'm quite new to c++ myself but I'll have a go at explaining my understanding of a class.

A class is similar to a strucure but a class can have within it both variables and functions, whereas structures are typically made up of other variables.

Classes are usually the first 'objects' new programmers create. Think 'object orientated programming'.

When you write a class you have to first define it - the formal model of what something should do (just like when you define a function), Then you have to create an instance of that class, which is the object itself.

Classes are made up of variables and functions. An object based on that class can use those variables to store information and call the functions to perform actions.

Lets create a class called Rectangle.

class Rectangle { public:	int width, height;	//declare the attributes		//declare the methods	void setsize(int x, int y);	int area();	int perimeter();};


So we've declared a class called Rectangle with two attributes of type int and then three methods, one that returns nothing and two that will return an integer.

Then we have to define the methods (make them work)...

//the method setsize() assigns the rectangles dimensions to the attributes.//it takes 2 arguments, x and y.void Rectangle::setsize(int x, int y){	width = x;	height = y;}//the method area() returns the rectangles area as an integer.int Rectangle::area(){	return (width*height);}//the method perimeter() returns the rectangles perimeter as an integer.int Rectangle::perimeter(){	return ((width+height)*2);}


So now our class has been created and the methods have been defined we can create instances of that class, which are known as objects. You do this like any other variable e.g.

int x, y, z;
float a, b, c;
Rectangle r1, r2, r3;

so r1, r2 and r3, are all objects of type Rectangle, this means that they can access the functions declared earlier in our class.

Now, imagine all the code we wrote above was put in its own header file called rectangles.h and then we want to write a program that includes the functionality of our rectangle class, we could do this...

#include <iostream>using namespace std;#include "rectangles.h"int main(){	Rectangle myRectangle; //We can now declare an object of type Rectangle	int x,y; 		cout << "Enter width : ";	cin >> x;	cout << "Enter height : ";	cin >> y;		myRectangle.setsize(x,y); //assign the values x and y to the setsize function in the Rectangle class.		cout << endl << endl << "Area = " << myRectangle.area(); //print the results to the screen	cout << endl << "Perimeter = " << myRectangle.perimeter();	cin.ignore();	cin.get();	return 0;}


has the penny dropped yet??

This means that for every application you write you can use your new class and all its functions. The problem I had with classes was realising the potential, simple rectangle classes hardly seem the effort, but imagine a class with functions that perform hundreds or even thousands of calculations, to include all that code in every project would be a pain in the back-side.

Hope this helps, this is my first post trying to explain something, if anyone can spot any mistakes let me know.

Thanks, Gareth.
Quote:
A class is similar to a strucure but a class can have within it both variables and functions, whereas structures are typically made up of other variables.

This is misleading; in C++ the keywords class and struct create exactly the same type of construct. The only difference is that the members of a struct default to public and the members of a class default to private. Both can contain both fields and methods, et cetera.

Quote:
So we've declared a class called Rectangle with two attributes of type int and then three methods, one that returns nothing and two that will return an integer.

While syntactically correct, this is a bad example. You should read ToohrVyk's post. In particular you've made the mistake of making width and height public, but also providing a "setsize" method; the object model you've chosen is redundant and thus more brittle and less maintainable.

Quote:
Now, imagine all the code we wrote above was put in its own header file called rectangles.h and then we want to write a program that includes the functionality of our rectangle class, we could do this...

Be careful how you word this; this is just implementation detail that is, in fact, entirely unrelated to the theory behind modeling your program as a collection of objects and the concrete implementation of classes in C++. Header files are not required.

Quote:
but imagine a class with functions that perform hundreds or even thousands of calculations,

This would probably violate the principal of single-responsibility, which would in turn engender a bad design.

Quote:
Hope this helps, this is my first post trying to explain something, if anyone can spot any mistakes let me know.

I don't mean to sound like I'm picking on you, and I know your intentions were good, but your explanation was just slightly off in a number of subtle ways that tend to drive me up the wall. Your statement that you're new to C++ only compounds that, for me. C++ is a nasty, complex language that is quite difficult to understand (and now I'm just talking about the language, not the language-agnostic concepts involved in the object-oriented design paradigm); I find that lots of people try to help (for example, by writing "tutorials") after only a short period of time with the language, and while their intentions are good they often provide horribly inaccurate or misleading information without realizing it, which is harmful to all parties involved.

The common symptom of this issue is providing more information than is required and presenting that information in such a way as to give the impression that it is authoritative and definitive. The thing about include files is an example: that entire mechanism is unrelated to the way that the class (or struct) keywords work in C++. My advice to you to improve the quality of your explanations is to carefully consider the content when drafting them. Try to omit anything that is not directly relevant or related, unless you absolutely cannot, and in that case try your best to explain that this is not the only valid way to go about whatever it is you're describing.
Quote:I don't mean to sound like I'm picking on you, and I know your intentions were good, but your explanation was just slightly off in a number of subtle ways that tend to drive me up the wall. Your statement that you're new to C++ only compounds that, for me. C++ is a nasty, complex language that is quite difficult to understand (and now I'm just talking about the language, not the language-agnostic concepts involved in the object-oriented design paradigm); I find that lots of people try to help (for example, by writing "tutorials") after only a short period of time with the language, and while their intentions are good they often provide horribly inaccurate or misleading information without realizing it, which is harmful to all parties involved.


Thanks for the advice/corrections maybe I should expand my understanding alot more before offering my knowlege to others.

cheers, Gareth.
C++: a Dialog, for lazy non-Googlers.

Also, Python translations of ToohrVyk's code snippets:

# Python doesn't support the idea of an object with no class.# The stack class:class stack_class:  def __init__(self):    self.contents = []  def push(self, i):    self.contents.append(i)  def pop():    if (self.contents == []): raise StandardError, "Empty stack"    (result, self.contents) = (self.contents[-1], self.contents[:-1])    return result    # Actually, Python lists already have a built-in 'pop', but I wanted to    # show the logic.stack_instance_1 = stack_class()stack_instance_2 = stack_class()# Sample class. No language-enforced implementation-interface separation,# and also no need to declare member variables; Python objects are# "dictionaries", where members are looked up by name when needed (and an# exception raised if code refers to a non-existant member).class <u>classname</u><i>(someinterface)</i>:  <font color="blue">def __init__(self):</font>    <font color="red">membervariable</font><font color="blue"> = initialvalue</font>  <font color="red">def implementation_method(self):    return 0</font>  <font color="green">def interface_method(self):</font>    <font color="red">return 1 + 1# this, self# Python lets you choose the name for this-references, like O'Caml; and like# O'Caml, 'self' is conventional. Unlike O'Caml, 'self' needs to be specified# explicitly as a parameter (the first) for each member function. This is IIRC a# consequence of how Python does function binding and name lookup.


Also, ToohrVyk, help me write a book wouldya? ;)
Thanks ToohrVyk for you amazing amount of help.

I have copied your post onto my computer for later reading as I am currently busy.
##########################################################You'll have time to rest when you're dead - Robert De Niro
I have read through the post and realised that I need a better understanding of pointers to continue.

Also, whats O' Caml???
##########################################################You'll have time to rest when you're dead - Robert De Niro
Quote:Original post by bxela1
I have read through the post and realised that I need a better understanding of pointers to continue.


In fact, you would need knowledge of references more than pointers (the basics of pointers are of course mandatory, but references are used more often). If you know of references, then just consider pointers to be references which can be reseated (made to reference another object) and can reference nothing as well (null pointers).

Quote:Also, whats O' Caml???


It's a programming language which incorporates procedural, functional and object-oriented paradigms. It's evolved from Caml Light (a language which is used in French schools to teach programmers, which is one of my jobs), which in turn is evolved from the early eighties "ML" languages. It uses type inference (so you don't have to write types down: they're guesses) while being even more type-safe than C family languages. It has the same level of performance as other high-level languages, except for heavy numeric code (because of a nasty thing called floating-point number boxing).

This topic is closed to new replies.

Advertisement