Programming C, C++, Java, PHP, Ruby, Turing, VB
Computer Science Canada 
Programming C, C++, Java, PHP, Ruby, Turing, VB  

Username:   Password: 
 RegisterRegister   
 [WIP] C++ OOP Whirlwind
Index -> Programming, C++ -> C++ Tutorials
View previous topic Printable versionDownload TopicRate TopicSubscribe to this topicPrivate MessagesRefresh page View next topic
Author Message
wtd




PostPosted: Thu Oct 26, 2006 5:27 pm   Post subject: [WIP] C++ OOP Whirlwind

Let's create a class!

code:
class Foo
{

};


This class does precisely nothing useful. However, what it does do is create a new type. We can declare a variable of this type.

code:
Foo f;


We can dynamically allocate memory for that type.

code:
Foo *f = new Foo;


We can delete it.

code:
delete f;


We can write a function which takes such a value as a parameter.

code:
int bar(Foo f)
{
   return 42;
}


We can use that type in overloading existing functions.

code:
std::ostream& operator<<(std::ostream& out, const Foo& f)
{
   return out << 42;
}
Sponsor
Sponsor
Sponsor
sponsor
wtd




PostPosted: Thu Oct 26, 2006 5:28 pm   Post subject: (No subject)

Member functions!

code:
class Foo
{
   int bar();
};


So now we can just create a Foo object and call that method, right?

code:
Foo f;
std::cout << f.bar() << std::endl;


Not so fast. By default, everything in a class is private. We'll have to explicitly make that member fucntion public.

code:
class Foo
{
   public:
      int bar();
};


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.

code:
int Foo::bar()
{
   return 27;
}


Now, let's try calling that again.

code:
Foo *f;
std::cout << f->bar() << std::endl;


Oops! The variable "f" hasn't yet been initialized.

code:
Foo *f = new Foo;
std::cout << f->bar() << std::endl;


Dodged a bullet there. No one likes dealing with segmentation faults.

Only one problem remaining.

code:
const Foo f;
std::cout << f.bar() << std::endl;


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.

code:
class Foo
{
   int bar() const;
};

int Foo::bar() const
{
   return 27;
}
wtd




PostPosted: Thu Oct 26, 2006 5:29 pm   Post subject: (No subject)

State

code:
class Foo
{
   int _bar;
   
   public:
      int bar() const;
};

int Foo::bar() const
{
   return _bar;
}


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.
wtd




PostPosted: Thu Oct 26, 2006 5:29 pm   Post subject: (No subject)

Construction

code:
class Foo
{
   private:
      int _bar;
   
   public:
      Foo(int bar);
   
      int bar() const;
};

Foo::Foo(int bar) : _bar(bar)
{
}

int Foo::bar() const
{
   return _bar;
}


Constructors provide a way to initialize the state of an object.

We may now write the following.

code:
Foo f = Foo(31);
std::cout << f.bar() << std::endl;


And the output will be as follows.

code:
31


We may have more than one constructor, following C++ function overloading rules. Though constructors are not actually functions.

code:
class Foo
{
   private:
      int _bar;
   
   public:
      Foo();
      Foo(int bar);
   
      int bar() const;
};

Foo::Foo() : _bar(0)
{
}

Foo::Foo(int bar) : _bar(bar)
{
}

int Foo::bar() const
{
   return _bar;
}


Here we've created a default constructor. Such a constructor will be called in either of the following situations.

code:
Foo f1;
Foo *f2 = new Foo;


It's important to note that we might also accomplish the above effect using default parameter values.

code:
class Foo
{
   private:
      int _bar;
   
   public:
      Foo(int bar = 0);
   
      int bar() const;
};

Foo::Foo(int bar) : _bar(bar)
{
}

int Foo::bar() const
{
   return _bar;
}


However, we cannot achieve the following effect using default values.

code:
class Foo
{
   private:
      int _bar;
   
   public:
      Foo(int bar = 0);
          Foo(const Foo& other);
   
      int bar() const;
};

Foo::Foo(int bar) : _bar(bar)
{
}

Foo::Foo(const Foo& other) : _bar(other._bar)
{
}

int Foo::bar() const
{
   return _bar;
}


This is known as a copy constructor. It literally copies the state of another object into the new object.
wtd




PostPosted: Thu Oct 26, 2006 5:31 pm   Post subject: (No subject)

Being explicit

code:
class Foo
{
   private:
      int _bar;
   
   public:
      explicit Foo(int bar = 0);
          explicit Foo(const Foo& other);
   
      int bar() const;
};

Foo::Foo(int bar) : _bar(bar)
{
}

Foo::Foo(const Foo& other) : _bar(other._bar)
{
}

int Foo::bar() const
{
   return _bar;
}


Previously our constructors have not been qualified as explicit. Without this, we could have had code of the sort that follows.

code:
Foo f = 42;
std::cout << f.bar() << std::endl;


With the explicit qualifier, we now require one of the following.

code:
Foo f = Foo(42);
Foo f(42);


But this also makes it impossible to write the following.

code:
Foo f = Foo(42);
Foo g = f;


Instead we would have to:

code:
Foo f = Foo(42);
Foo g = Foo(f);


Instead, perhaps we should :

code:
class Foo
{
   private:
      int _bar;
   
   public:
      explicit Foo(int bar = 0);
          Foo(const Foo& other);
   
      int bar() const;
};
wtd




PostPosted: Thu Oct 26, 2006 5:31 pm   Post subject: (No subject)

Assignment

code:
class Foo
{
   private:
      int _bar;
   
   public:
      explicit Foo(int bar = 0);
          Foo(const Foo& other);
          
          Foo& operator=(const Foo& other);
   
      int bar() const;
};

Foo::Foo(int bar) : _bar(bar)
{
}

Foo::Foo(const Foo& other) : _bar(other._bar)
{
}

Foo& Foo::operator=(const Foo& other)
{
   _bar = other._bar;
   return *this;
}

int Foo::bar() const
{
   return _bar;
}


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."
wtd




PostPosted: Thu Oct 26, 2006 5:32 pm   Post subject: (No subject)

A few more operators

code:
class Foo
{
   private:
      int _bar;
   
   public:
      explicit Foo(int bar = 0);
          Foo(const Foo& other);
          
          Foo& operator=(const Foo& other);
   
      int bar() const;
          
          bool operator==(const Foo& other) const;
          bool operator<(const Foo& other) const;
};

Foo::Foo(int bar) : _bar(bar)
{
}

Foo::Foo(const Foo& other) : _bar(other._bar)
{
}

Foo& Foo::operator=(const Foo& other)
{
   _bar = other._bar;
   return *this;
}

int Foo::bar() const
{
   return _bar;
}

bool Foo::operator==(const Foo& other) const
{
   return _bar == other._bar;
}

bool Foo::operator<(const Foo& other) const
{
   return _bar < other._bar;
}


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.
md




PostPosted: Thu Oct 26, 2006 9:55 pm   Post subject: (No subject)

Very nice wtd, I didn't know about explicit before... /me should read the ISO C++ standards.
Sponsor
Sponsor
Sponsor
sponsor
wtd




PostPosted: Fri Oct 27, 2006 4:15 pm   Post subject: (No subject)

Friends

code:
class Foo
{
   private:
      int _bar;
   
   public:
      explicit Foo(int bar = 0);
          Foo(const Foo& other);
          
          Foo& operator=(const Foo& other);
   
      int bar() const;
          
          bool operator==(const Foo& other) const;
          bool operator<(const Foo& other) const;
          
          void print() const;
};


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.

code:
void print(std::ostream& out) const;


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.

code:
std::ostream& operator<<(std::ostream& out, const Foo& f)
{
   return out << f.bar();
}


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...

code:
class Foo
{
   private:
      int _bar;
   
   public:
      explicit Foo(int bar = 0);
          Foo(const Foo& other);
          
          Foo& operator=(const Foo& other);
   
      int bar() const;
          
          bool operator==(const Foo& other) const;
          bool operator<(const Foo& other) const;
          
          friend std::ostream& operator<<(std::ostream& out, const Foo& f);
};

std::ostream& operator<<(std::ostream& out, const Foo& f)
{
   return out << f._bar;
}


Now we can easily:

code:
int main()
{
   Foo f(42);
   
   std::cout << "The value of f is: " << f << std::endl;
   
   return 0;
}


Just as easily, input can be handled.

code:
class Foo
{
   private:
      int _bar;
   
   public:
      explicit Foo(int bar = 0);
          Foo(const Foo& other);
          
          Foo& operator=(const Foo& other);
   
      int bar() const;
          
          bool operator==(const Foo& other) const;
          bool operator<(const Foo& other) const;
          
          friend std::ostream& operator<<(std::ostream& out, const Foo& f);
          friend std::istream& operator>>(std::istream& in, Foo& f);
};

std::ostream& operator<<(std::ostream& out, const Foo& f)
{
   return out << f._bar;
}

std::istream& operator>>(std::istream& in, Foo& f)
{
   return in >> f._bar;
}


code:
int main()
{
   Foo f;
   
   std::cout << "Please input a value for f." << std::endl;
   std::cin >> f;
   std::cout << "The value of f is: " << f << std::endl;
   
   return 0;
}
wtd




PostPosted: Fri Oct 27, 2006 4:37 pm   Post subject: (No subject)

Simple inheritance

code:
class Foo
{
   private:
      int _bar;
   
   public:
      explicit Foo(int bar = 0);
          Foo(const Foo& other);
          
          Foo& operator=(const Foo& other);
   
      int bar() const;
          
          bool operator==(const Foo& other) const;
          bool operator<(const Foo& other) const;
          
          friend std::ostream& operator<<(std::ostream& out, const Foo& f);
          friend std::istream& operator>>(std::istream& in, Foo& f);
};

class Bar : public Foo
{

};


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.

code:
class Bar : public Foo
{
   public:
      explicit Bar(int bar = 0);
          Bar(const Bar& b);
};


And then we'll implement them.

code:
Bar(int bar) : Foo(bar)
{
}

Bar(const Bar& b) : Foo(b.bar())
{
}


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.

code:
Foo f = Bar(42);
Bar b = Bar(42);


But I cannot:

code:
Bar b = Foo(42);


While a Bar is a Foo, a Foo is not a Bar.
wtd




PostPosted: Fri Oct 27, 2006 5:23 pm   Post subject: (No subject)

Overriding

code:
class A
{
   public:
      std::string id() const;
};

class B : public A
{
   public:
      std::string id() const();
};


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.

code:
std::string A::id() const
{
   return "A";
}

std::string B::id() const
{
   return "B";
}


So, now if we write:

code:
A a;
B b;

std::cout << a.id() << " " << b.id() << std::endl;


We'll get as output:

code:
A B


That's pretty straightforward, but remember that stuff about is-a? Well, let's see how that changes things.

code:
A a;
A b = B();

std::cout << a.id() << " " << b.id() << std::endl;


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:

code:
A A


So, why did this happen? Well, As far as the program is concerned, the variable "b" is of type A.

Virtual methods

code:
class A
{
   public:
      virtual std::string id() const;
};

class B : public A
{
   public:
      virtual std::string id() const();
};


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.

code:
A *a = new A;
A *b = new B;

std::cout << a->id() << " " << b->id() << std::endl;


Which results in:

code:
A B
wtd




PostPosted: Sat Oct 28, 2006 12:32 pm   Post subject: (No subject)

Augmenting

code:
class Animal
{
   private:
      int _energy;

   public:
      Animal(int energy = 0);
          Animal(const Animal& other);
   
      virtual Animal& operator=(const Animal& other);
          
      virtual int energy() const;
          
          // Increases energy.
      virtual void eat();
};

class HairyAnimal : public Animal
{
   private:
      std::string _hair_color;
          int _hair_length;
          
   public:
      HairyAnimal(int energy = 0,
                      std::string hair_color = "Brown",
                                  int hair_length = 0);
          HairyAnimal(const HairyAnimal& other);
          
          virtual HairAnimal& operator=(const HairyAnimal& other);
          
          virtual std::string hair_color() const;
          virtual int hair_length() const;
          
          // Increases hair length.
          virtual void grow_hair();
};


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.
wtd




PostPosted: Sat Oct 28, 2006 4:36 pm   Post subject: (No subject)

Interface inheritance

code:
class HairyThing
{
   public:
      virtual std::string hair_color() const = 0;
          virtual int hair_length() const = 0;
          
          // Increases hair length.
          virtual void grow_hair() = 0;
};

class Animal
{
   private:
      int _energy;

   public:
      Animal(int energy = 0);
          Animal(const Animal& other);
   
      virtual Animal& operator=(const Animal& other);
          
      virtual int energy() const;
          
          // Increases energy.
      virtual void eat();
};

class Ball
{
   private:
      int _radius;

   public:
      Ball(int radius = 1);
      Ball(const Ball& other);

      virtual Ball& operator=(const Ball& other); 
          
          virtual int radius() const;
          virtual void set_radius(int radius);
};

class HairyAnimal : public Animal, public HairyThing
{
   private:
      std::string _hair_color;
          int _hair_length;
          
   public:
      HairyAnimal(int energy = 0,
                      std::string hair_color = "Brown",
                                  int hair_length = 0);
          HairyAnimal(const HairyAnimal& other);
          
          virtual HairAnimal& operator=(const HairyAnimal& other);
          
          virtual std::string hair_color() const;
          virtual int hair_length() const;
          
          // Increases hair length.
          virtual void grow_hair();
};

class HairBall : public HairyThing
{
   private:
      std::string _hair_color;
          
   public:
      HairBall(int radius = 1);
      HairBall(const HairBall& other);

      virtual HairBall& operator=(const HairBall& other);
          
          virtual std::string hair_color() const;
          virtual int hair_length() const;
          
          // Increases hair length.
          virtual void grow_hair();
};


So, what's going on here?

code:
class HairyThing
{
   public:
      virtual std::string hair_color() const = 0;
          virtual int hair_length() const = 0;
          
          // Increases hair length.
          virtual void grow_hair() = 0;
};


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.

code:
HairyThing *a = new HairyAnimal;
HairyThing *b = new HairBall;


Both of the above are perfectly legal.
Display posts from previous:   
   Index -> Programming, C++ -> C++ Tutorials
View previous topic Tell A FriendPrintable versionDownload TopicRate TopicSubscribe to this topicPrivate MessagesRefresh page View next topic

Page 1 of 1  [ 13 Posts ]
Jump to:   


Style:  
Search: