Features of C++

Solving problems with programs is made easier if we can reuse the same or similar solutions that already exist. We do this using the 'chunks' provided by a language, as described in the previous resources. These sections describe the larger 'chunk' features of C++. Larger 'chunks' consist of programming statements used to write a program and to complete programs or portions of programs that reside in libraries.

The section "Classes and Inheritance" explains and illustrates classes, which enable reuse of large sections of programming code. "Templates" explains and illustrates generic programming using templates. Focus specifically on the Introduction, Function Templates, and Class Templates. It also discusses STL, the standard C++ library. Note that 'list' is a template in C++.

C++: Classes and Inheritance

Classes are the building blocks of programs built using the object-oriented methodology. Such programs consist of independent, self-managing modules and their interactions. An object is an instance of such module, and a class is its definition.


Abstract example

To illustrate classes and objects, we will use a dog.

Dog:

----

char name
int gender
int age
int size
bool healthy

A dog has several variables: name, the gender of the dog, the age of the dog, its size, and whether or not it is healthy. These variables are called properties. This definition would go in a class. An instance of a dog, say, a dog named Lucy, would be an object. So would a dog named Ruffy. You can have multiple instances of a class, just like you can have multiple dogs. Properties are like "inner variables" of each object made of type Dog.


The example in terms of C++

struct Dog

struct Dog
{
    char name[25];
    int gender;
    int age;
    int size;
    bool healthy;
};

We have just defined what a dog is. Let us make a couple of dogs...

int main(int argc, char *argv)
{
   Dog lucy;
   Dog ruffy;
   lucy.gender = 1;
   lucy.age = 3;
   ruffy.gender = 2;
   ruffy.age = 5;

   return 0;
}

In this short example, we have created two objects of the type Dog. One is lucy, and the other is ruffy. We have set lucy's gender to 1, and ruffy's gender to 2. Also, we've set lucy's age to 3, and ruffy's to 5. However, having such information publicly accessible can be dangerous. A bug in your program, for example, could cause the unintended change of the properties of one of the dogs! You wouldn't want a bug in your software to cause lucy to be sick, would you?


class Dog

Structs are containers whose properties are always public. In order to gain control over what is public and what isn't, we will have to use a class. Let us see how the Dog class would look like in a class instead of a struct:

class Dog
{
private:
    char name[25];
    int gender;
    int age;
    int size;
    bool healthy;
};

Now our dogs precious properties aren't public for everyone to view and change. However, this raises a little problem. We can't change them either. Only other dogs can see and change them. In order to be able to access a Dog's private properties, we would have to make a function to do so. This brings us to our next chapter.


Dogs with Methods

Classes can have functions in them. These functions are called methods. Methods can access all, and even private, properties and methods (yes, methods can be private) of its class. Let us make some methods to get our dog's information...

class Dog
{
private:
    char name[25];
    int gender;
    int age;
    int size;
    bool healthy;

public:
    char* getName()   { return name;   }
    int   getGender() { return gender; }
    int   getAge()    { return age; }
    int   getSize()   { return size; }
    bool  isHealthy() { return healthy; }
    void  setHealthy(bool dhealthy) { healthy = dhealthy; }
    void  setName(char* dname)      { name = dname; }
};

#include <iostream>

int main()
{
    Dog lucy;

    std::cout << "lucy's name   is " << lucy.getName()   << std::endl;
    std::cout << "lucy's gender is " << lucy.getGender() << std::endl;
    std::cout << "lucy's age    is " << lucy.getAge()    << std::endl;
    std::cout << "lucy's size   is " << lucy.getSize()   << std::endl;
    if(lucy.isHealthy())
        std::cout << "lucy is healthy"       << std::endl;
    else
        std::cout << "lucy isn't healthy :(" << std::endl;

    std::cout << "Now I'm changing lucy abit..." << std::endl;

    lucy.setHealthy(!(lucy.isHealthy()));
    lucy.setName("lUCY");

    std::cout << "lucy's name   is " << lucy.getName()   << std::endl;
    std::cout << "lucy's gender is " << lucy.getGender() << std::endl;
    std::cout << "lucy's age    is " << lucy.getAge()    << std::endl;
    std::cout << "lucy's size   is " << lucy.getSize()   << std::endl;
    if(lucy.isHealthy())
        std::cout << "lucy is healthy"       << std::endl;
    else
        std::cout << "lucy isn't healthy :(" << std::endl;

    return 0;
}

However, if we run this program, we might have a little problem. We have never initialized lucy's properties...


Constructors

In order to initialize lucy's properties, we could write a method that would do it for us, and call it every time we make a dog. However, C++ already provides us such a method. A constructor is a method which is called every time a new object is created. To use this nice little thing provided to us by C++, we will have to write a method which returns no value, and has the same name as the class. Here's Dog written using a constructor:

class Dog
{
private:
    char name[25];
    int gender;
    int age;
    int size;
    bool healthy;

public:
    char* getName()   { return name;   }
    int   getGender() { return gender; }
    int   getAge()    { return age; }
    int   getSize()   { return size; }
    bool  isHealthy() { return healthy; }
    void  setHealthy(bool dhealthy) { healthy = dhealthy; }
    void  setName(char* dname)      { name = dname; }

    Dog()
    {
        name    = {'L', 'u', 'c', 'y', '\0'};
        gender  = 1;
        age     = 3;
        size    = 4;
        healthy = true;
    }
};

From now on, every dog we instantiate will have the name "Lucy", be of gender 1, aged 3, sized 4 and healthy. Well, what if you want some diversity? No problem. Constructors with parameters!

    Dog(char* dname, int dgender, int dage, int dsize, bool dhealthy)
    {
        name    = dname;
        gender  = dgender;
        age     = dage;
        size    = dsize;
        healthy = dhealthy;
    }

If you have both constructors in your Dog class, you can now either create a dog as always, which will have the default values (named "Lucy" etc etc), or you could use RAII to make your customized dog as follows:

#include <iostream>

int main()
{
    Dog scruffy("Scruffy", 2, 8, 6, false);
    
    std::cout << "scruffy's name   is " << scruffy.getName()   << std::endl;
    std::cout << "scruffy's gender is " << scruffy.getGender() << std::endl;
    std::cout << "scruffy's age    is " << scruffy.getAge()    << std::endl;
    std::cout << "scruffy's size   is " << scruffy.getSize()   << std::endl;
    if(scruffy.isHealthy())
        std::cout << "scruffy is healthy"       << std::endl;
    else
        std::cout << "scruffy isn't healthy :(" << std::endl;

    return 0;
}


Inheritance

The inheritance mechanism is a core component of the Object Oriented Programming paradigm. Inheritance means that a class "inherits" another class's definition and type. In the previous chapter, we've covered the "HAS-A" relationship. A dog HAS-A name. In this chapter, we will cover the IS-A relationship. A dog IS-A pet.


Abstract example

A pet is a wonderful concept, don't you think? It's an abstract concept, but we will not cover abstraction here. There are many kinds of pets (dogs, cats, horses...), and they all have some things in common.

Pet:

----

char* name;

----

char* getName();
void voice();
void interact(Pet* other);
void annoy();
Pet(char* dname);


In terms of C++

Implementation of Pet

As we already know classes, it shouldn't be a problem to implement Pet in C++.

#include <iostream>

class Pet
{
private:
    char* name;

public:
    char*        getName()            { return name; }
    virtual void voice()              { std::cout << name << ": DEFAULT VOICE" << std::endl; }
    void         interact(Pet* other) { voice(); other->voice(); }
    void         annoy()             { std::cout << name << " annoys you." << std::endl; }
    // I've omitted the constructor for now
};

Notice anything different? Yep, the virtual keyword. This keyword tags a method as overridable. Inheriting classes can override inherited methods. I'll explain it in a moment.


Inheritance

OK. We've got a Pet with a "DEFAULT VOICE". However, a dog barks. Well, we could make a dog class, however note that interact() receives a pointer to a Pet. As Pet and Dog are two different classes, pets will be unable to interact with dogs. That's not what we want to do, so we need to somehow make Dog a Pet.
One word: Inheritance.
Remember how I told you that it'd inherit its type? This is what I have meant. Inheritance is done using the ':' operator, as such:

class Dog : public Pet {};


Overriding

Now we can make Dogs, and they would all have unique names, they could voice, interact with other pets and annoy their lovely owner. However, there's yet one problem. They still have the annoying "DEFAULT VOICE". Well now that we inherit from Pet, we can override its virtual methods. voice() is a virtual method, so why not do that now? Overriding looks very similar to declaring a new method, so watch out.

class Dog : public Pet
{
public:
    void voice() { std::cout << getName() << ": Woof!" << std::endl; }
};


Constructors and inheritance

Now our Dog class functions as intended. However, there's yet one complication...
We haven't got a constructor yet.

class Pet
{
    // ...
public:
    // ...
    Pet(char* dname)
    {
        name = dname;
    }
};

Constructor calling constructors

Now that Pet knows how to initialize itself, Dog needs to too. Dog can not directly modify its name, so it has to make Pet do it for him. If Pet would have had a constructor that took no arguments, there wouldn't have been a problem and the process would have been automatic. However, Pet has no "default constructor" (a constructor that takes no arguments), and Dog is in a little difficult situation. I'll save you the trouble. This is how it's done:

class Dog
{
    // ...
public:
    // ...
    Dog(char* dname) : Pet(dname)
    {
    }
};

Here we have Dog's constructor getting a desired name, and passing it to Pet's constructor to do the work.


Inheritance in a program

int main()
{
    Pet pet("Michael");
    Dog dog("Wooflemeister");

    dog.voice();
    pet.annoy();
    pet.interact(&dog);
    dog.annoy();
    
    return 0;
}

Output:

Wooflemeister: Woof!
Michael annooys you.
Michael: DEFAULT VOICE
Wooflemeister: Woof!
Wooflemeister annoys you.


Exercise

  • Design and implement a system of vehicles.
A family car has a color, a maximum speed and a sunroof. The sunroof can be opened and closed.
A cab's color is always yellow and it has a maximum speed.
A bicycle has a color, a maximum speed and pedals that can be spun.
A truck has a color, a maximum speed, and it can carry a cab or a family car, but not a bicycle.
A road can contain any of the above.
A policeman has a hat, and can block a road or any vehicle.
Tip: You can chain inheritance, and create trees.


Templates

At times we find difficulties in programming, we want to make something work the same for different objects and we often have to design the same thing several times in order to make it work. Suppose you want to make a simple Vector class, as in std::vector. Practically, in C, there is only one way to do it: Keeping type information stored somewhere in your list. This is a very common problem in C, and several people came up with several solutions for that, but now, in C++, the solution to all type-insensitive (those who do not rely on class functions / members) classes and functions came up. If you are one of those interested in the concept of generics (same as type-insensitive), you are on the right place. Welcome to C++ templates.


Introduction

Templates are the mechanism by which C++ implements the generics concept. The following example illustrates two non-generic (type-sensitive) functions for multiplying two numbers, x and y:

int multiply ( int x, int y )
{
    return (x * y);
}
double multiply ( double x, double y )
{
    return (x * y);
}

Two functions that do exactly the same thing, but cannot be defined as a single function because they use different data types.


Function Templates

Templates were made to fullfil the need to design generic code, that work the same way in different situations. Many people get scared when they first look at a template, as the syntax looks too complex for a very simple thing. Although the syntax is indeed a little complex, it is just different, not harder, not easier than other syntax presented in C++.


Syntax

To start a template, you must provide the following declaration:

template<class Type>

or

template<typename Type>

The keywords class and typename have exactly the same meaning in this case, but some compilers may replace the word class for typename when giving you information about the class, as it's often better accepted by programmers.

Type is our generic data type's name, and when the template is to be used, it would be the same as if Type was a typedef for your datatype, and that's exactly how it behaves, therefore, whenever you use the word Type, you are talking about the data type being used on your template. The following example now illustrates how the multiply function would be written using a template:

template<class Type>
Type multiply ( Type x, Type y )
{
    return (x * y);
}

To use the function, however, the full syntax is a bit weird:

int result = multiply<int> ( 2, 5 ); /* Yields 10 */

As a syntax sugar, C++ provides the option of hiding the type information, case in which the data type used on the function call is assumed. For instance:

int r1 = multiply ( 2, 5 );
int r2 = multiply<int> ( 2, 5 );

Both yield the same result, as they are exactly the same. An important note to take from here is that this is the one of the very few syntax sugars that templates have.

Optionally, a template can have more type options, and the syntax is pretty simple. For a template with three types, called FirstSecond and Third, we have:

template<class First, class Second, class Third>

And it works exactly as the single-typed template. The template data types are order sensitive, of course, so doing:

multiply<char, int> ( 2, 5 );

Is not the same as doing:

multiply<int, char> ( 2, 5 );

As the order the types are assigned changed.


Class Templates

As another powerful feature of C++, you can also make template classes, which are classes that can have members of the generic type.

The Standard Template Library (STL) relies heavily on the power of template classes for its many functionalities, as the containers std::vector and std::list, for example. Examples on the implementation of both containers will be presented later on, as well as some more.


Syntax

The syntax for template classes is exactly the same as for template functions. A small difference is that you can never hide the type of a template class when you instantiate it.

Therefore, a small class Node, with three members, previousvalue and next can be written as:

template<class Type>
class Node
{
public:

    Type value;

    Node* previous;
    Node* next;
};

Notice one important thing: Although the data type for Node pointers previous and next is hidden, the type Type is assumed. The same declarations could be written as:

Node<Type>* previous;
Node<Type>* next;

Also notice that the exact same syntax used for function calling is used for class instantiating. You can hide data types in specific cases, but for legibility and to establish a solid coding convention, you shouldn't, unless you are on the same class as the variable being declared / instantiated.

There is also the possibility of setting a default type, as in:

template<class T1, class T2 = int>

Or

template<class T1, class T2 = DefaultTemplateClass<T1> >

Both syntax are equally valid, and you can use previous types to define next ones (as T1 is the template type for type T2). Note the space between the two closing >, which prevent the compile to read them as one token (left-shift operator).

Note it only works effectively (but you can do it otherwise) only when you have more than one type, as when you have only one you would be lacking the template argument, causing the compiler to give you an error:

template<class T1 = int>
class Useless
{
public:

    T1 value;
};

Useless value; /* Wrong! */

Although the default type for the template class is valid, you can not lack the type list when instantiating, but it can be empty, as in:

template<class T1 = int>
class Useless
{
public:

    T1 value;
};

Useless<> value; /* Valid */

Note: The default type setting is available only for template classes, functions do not present this feature, as they already set the defaults, as explained before.


Implementation

Now let's get our hands to work on a simple yet useful class, which we'll call Vector. Not to worry with minor problems such as dynamic memory allocation, which is needed in this case (to make the Vector grow when needed and optionally make it shrink when needed), we'll use an imaginary pointer that we believe contains an allocated array for us to use as a storage.

Our Vector class will have two functions: push and pop. We will also define an interesting thing we find in STL's std::vector, the iterators (to walk through the list), together with iterating functions begin and end.

To reach this goal, we will rely on a template class and some new concepts which will be explained with the code. This is how it looks:

template<class Type>
class Vector
{
public:

    typedef typename Type* iterator;

    Vector ( Type* storage, unsigned long storageSize )
    : data(storage), size(0), maxSize(storageSize)
    {
    }

    ~Vector ( void )
    {
        /* Optionally free the allocated buffer here */
    }

    /* Puts the object on the last place in the  */
    void push ( const Type& object )
    {
        if (size >= maxSize)
          throw std::out_of_range("Attempted to push past the maxSize for Vector");
        data[size] = object;
        size++;
    }

    /* Virtually pops a member by saying size is one less */
    void pop ( void )
    {
        if (0 != size)
          size--;
    }

    /* Returns an iterator to vector's begin */
    iterator begin ( void )
    {
        if (0 == size)
          return end();
        return &data[0];
    }

    /* Returns an iterator to vector's end (after last element) */
    iterator end ( void )
    {
        return (data + size);
    }

private:

    const Type* data;

    unsigned long size;

    const unsigned long maxSize;
};

This illustrates perfectly just how simple templates really are, now get your hands to work and, if you know how iterators work, play a little bit with the class, if not, don't worry, a fast and objective explanation will be given soon. If you got curious about the typedef we used to define iterator, head to the section relative to it in this lesson.

Aside: Iterators

Iterators are an essential part of STL containers, that use this concept as a base for container walkingthrough. Although they have the same name in all of the various containers, their definition is rather different, as each kind of container uses a different method for storing objects. As an example, take our Vector class definition for iterator: A pointer, as simple as that. As our data class member is an array (probably allocated with new[]), we can walk trough its members by simple incrementing / decrementing the pointer and get a reference to the value pointed to by dereferencing it. Function begin returns an iterator to the first element inside our array (the data pointer itself, in this case), and function end returns an iterator to the position where a new element will be stored in next push, so we know when our iterator equals end, there is no more elements to be seen. For the definitions of another iterators in STL, check STL sourcecode or your favorite documentation page.

A simple use of our vector class:

Vector<int> v;
Vector<int>::iterator vit;

for ( int i = 0; i < 10; i++ )
{
	v.push ( i );
}

for ( vit = v.begin(); vit != v.end(); vit++ )
{
	std::cout << (*vit) << std::endl;
}

Which then prints numbers from 0 to 10 in new lines, sequentially. It also illustrates the use of iterators to walk trough the list.

As requested by HappyCamper, here is an example of a container template class called List, that uses the same concept of std::list, the linked list.

template<class Type>
class Node
{
public:

    Node ( Node* previous, const Type& val )
    {
        value = val;

        prev = previous;

        if ( previous != NULL )
        {
            previous->next = this;
        }

        next = NULL;
    }

    ~Node ( void )
    {
    }

    Type value;

    Node* prev;
    Node* next;
};

template<class Type>
class _List_Iterator
{
public:

    _List_Iterator ( void )
    {
    }

    _List_Iterator ( Node<Type>* object )
    {
        node = object;
    }

    ~_List_Iterator ( void )
    {
    }

    _List_Iterator& operator = ( const _List_Iterator& it )
    {
        node = it.node;

        return (*this);
    }

    bool operator != ( const _List_Iterator& it )
    {
        return (node != it.node);
    }

    /* Sets internal Node pointer to next node */
    _List_Iterator& operator ++ ( void )
    {
        node = node->next;

        return (*this);
    }

    /* Sets internal Node pointer to previous node */
    _List_Iterator& operator -- ( void )
    {
        node = node->prev;

        return (*this);
    }

    Type& operator * ( void )
    {
        return node->value;
    }

    Node<Type>* node;
};

template<class Type>
class List
{
public:

    typedef typename _List_Iterator<Type> iterator;

    List ( void )
    {
        first = NULL;
        last = NULL;
    }

    ~List ( void )
    {
        Node<Type>* node;
        Node<Type>* next;

        node = first;

        while ( node )
        {
            next = node->next;

            delete node;

            node = next;
        }
    }

    iterator begin ( void )
    {
        return iterator ( first );
    }

    iterator end ( void )
    {
        return iterator ( NULL );
    }

    void push ( const Type& object )
    {
        Node<Type>* node;
        
        node = new Node<Type> ( last, object );

        if ( first == NULL )
        {
            first = node;
        }

        last = node;
    }

    void pop ( void )
    {
        Node<Type>* aux;

        aux = last;

        if ( first == last )
        {
            first = NULL;
            last = NULL;
        }
        else
        {
            last = last->prev;
            last->next = NULL;
        }

        delete aux;
    }

protected:

    Node<Type>* first;
    Node<Type>* last;
};

A new iterator class had to be designed in order to overload the operators (to be the same as our Vector class iterators, in practice). Also notice that, when the template class instantiated (inside another class) is not of the class type, it requires the argument list to be written.

Warning: Both of the classes, Vector and List, were written in request-time, e.g. they didn't go through exhaustive tests to assure its secure execution, it just illustrates the concept of template containers.

We can also easily design our counterpart of STL's std::pair, which we'll name Pair. It goes as follows:

template<class T1, class T2>
class Pair
class Pair
{
public:

    Pair ( void )
    {
    }

    Pair ( const T1& f, const T2& s )
    {
        first = f;
        second = s;
    }

    ~Pair ( void )
    {
    }

    T1 first;
    T2 second;
};

pair is a very simple structure that holds two values (hence the term pair) that may be of different types. For such achievement we use two arguments in our template. Function first returns a reference to the first value and, function second, a reference to the second one.

Templates can also be used with classes

using namespace std;

template <class T>
class myClass
{
		// Attributes
		T * storage;
		int NElements;
	public:
		// Constructors & Destructor
		myClass(int size = 1)
		{
			storage = new T[size];
			NElements = size;
		}
		~myClass() {delete [] storage;}
		// Methods
		int GetSize() {return NElements;}
		T GetVal(int place) {return storage[place];}

		void SetVal(int place, T val)
		{
			if (place + 1 > NElements)
			{ cout << "Cannot place a value at position " << place << endl; return; }
			storage[place] = val;
		}
		void push(T val) { SetVal(NElements++, val); }
		void pop() { NElements--; }
};
int main()
{
	myClass<int> mc1;
	for (int i = 0 ; i < 10 ; i++)
		{ mc1.push((i + 1)*(i + 1)); }

	for (int i = 0; i < mc1.GetSize() ; i++)
		{ cout << mc1.GetVal(i) << endl; }
	
	cout << "-------------\n";
	
	myClass<char> mc2;
	for (int i = 0 ; i < 26 ; i++)
		{ mc2.push((char) i+97); }

	for (int i = 0; i < mc2.GetSize() ; i++)
		{ cout << mc2.GetVal(i) << endl; }

	return 0;
}


Source: Internet Archive, http://web.archive.org/web/20160420135144/https://en.wikiversity.org/wiki/C%2B%2B/Classes_and_Inheritance
Creative Commons License This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License.

Last modified: Friday, August 11, 2023, 12:51 PM