C++ Programming/Structures and Classes

From Wikibooks, the open-content textbooks collection

Jump to: navigation, search

Contents

[edit] Structures and Classes

A struct or class is used to make types defined by a programmer called a user defined type. A program can contain any number of user defined types. An instance of a user defined type is called an object. Unlike the built-in types though, an object has some values and functions (called member functions) associated with it. As with other types, object types are case-sensitive.

[edit] Declaration

A struct is defined by:

struct myStruct {
/* public, protected and private
variables, constants and functions */
};

A class is defined by:

class myClass {
/* private, protected and public
variables, constants and functions */
};  

An object of type myStruct or myClass (case-sensitive) is declared using:

myStruct obj1;
myClass obj2;

As you can see, a struct and a class are practically the same thing. A struct can be used anywhere a class can be and vice-versa. The only technical difference is that class members defaults to private and struct members defaults to public. Structs can be made to behave like classes simply by putting in the keyword private at the beginning of the struct and a classes can be made to behave like structs simply by putting the keyword public at the beginning of the class.

Basically, C++ uses structs to comply with C's heritage (the code and the programmers). Structs are simpler to the programmer and to the compiler. One should use a struct for POD (PlainOldData) types that have no methods and whose data members are all public. struct may be used more efficiently in situations that default to public inheritance (which is the most common kind) and where public access (which is what you want if you list the public interface first) is the intended effect. Using a class, you typically have to insert the keyword public in two places, for no real advantage. Classes are typically used as the basic building block of OOP (this is a distinction of convention, not of language-enforced semantics).

Names
  • Name the struct or class after what it is. If you can't determine a name, then you have not designed the system well enough.
  • Compound names of over three words are a clue your design may be confusing various entities in your system. Revisit your design. Try a CRC card session to see if your objects have more responsibilities than they should.
  • Avoid the temptation of naming user defined types something similar to the type it is derived from. A user defined type should stand on its own. Declaring an object with a type doesn't depend on where it is derived from.
  • Suffixes or prefixes are sometimes helpful. For example, if your system uses agents then naming something DownloadAgent conveys real information.
Data Abstraction

A fundamental concept of Object Oriented (OO) recommends an object should not expose any of its implementation details. This way, you can change the implementation without changing the code that uses the object. The class, by design, allows its programmer to hide (and also prevents changes as to) how the class is implemented. This powerful tool allows the programmer to build in a 'preventive' measure. Variables within the class often have a very significant role in what the class does, therefore variables can be secured within the private section of the class.

[edit] Access Labels

Three labels are supported for user defined types to restrict access to member functions and data from outside the user defined type. The labels can be in any order and used multiple times. For example, a class "Bottle" could have a private variable fill, indicating a liquid level 0-3 dl. fill cannot be modified directly (compiler error - C2248), but instead Bottle provides the member function sip() to reduce the liquid level by 1. Mywaterbottle could be an instance of that class, an object.

 /* Bottle - Class and Object Example */
 #include <iostream>
 #include <iomanip>
 
 using namespace std;
 
 class Bottle
 {
     private: // variables are modified by member functions of class
         int fill; // dl of liquid
     public:
         Bottle() // Default Constructor
         : fill(3) // They start with 3 dl of liquid
         {
          // More constructor code would go here if needed.
         }
         bool sip() // return true if liquid was available
         {
             if (fill>0)
             {
                 --fill;
                 return true;
             }
             else
             {
                 return false;
             }
         }
         int level() const    // return level of liquid dl
         {
             return fill;
         }
 };  // Class declaration has a trailing semicolon
 
 int main()
 {
        // terosbottle object is an instance of class Bottle
         Bottle terosbottle;
         cout << "In the beginning, mybottle has "
              << terosbottle.level()
              << "  dl of liquid"
              << endl;
         while (terosbottle.sip())
         {
                 cout << "Mybottle has "
                      << terosbottle.level()
                      << " dl of liquid"
                      << endl;
         }
         return 0;
 }

These keywords, private, public, and protected, affect the permissions of the members -- whether functions or variables.

public
This label indicates any members within the 'public' section can accessed freely anywhere a declared object is in scope.
protected
This label is just like the private label, except when dealing with inheritance, which we will cover later.
private
This is used for members that must not be used from outside the user defined type. This is usually the domain of member variables and helper functions. It's often useful to begin putting functions here and then moving them to the higher access levels as needed.

NOTE:
Avoid declaring public data members, including data members in a public section can cause unforeseen disasters in the future for programs that dependent on your code.

It is often overlooked that different instances of the same user defined type may access each others' private or protected variables. A common case for this is in copy constructors.

struct Foo
{
  Foo( const Foo &f )
  {
    m_value = f.m_value; // perfectly legal
  }
private:
  int m_value;
};

[edit] Member Functions

Member functions can (and should) be used to interact with data contained within user defined types. User defined types provide flexibility in the "divide and conquer" scheme in program writing. In other words, one programmer can write a user defined type and guarantee an interface. Another programmer can write the main program with that expected interface. The two pieces are put together and compiled for usage. User defined types provide encapsulation defined in the Object Oriented Programming (OOP) paradigm.

Within classes, to protect the data members, the programmer can define functions to perform the operations on those data members. Member functions and functions are names used interchangeably in reference to classes. Function prototypes are declared within the class definition. These prototypes can take the form of non-class functions as well as class suitable prototypes. Functions can be declared and defined within the class definition. However, most functions can have very large definitions and make the class very unreadable. Therefore it is possible to define the function outside of the class definition using the scope resolution operator "::". This scope resolution operator allows a programmer to define the functions somewhere else. This can allow the programmer to provide a header file .h defining the class and a .obj or .lib file built from the compiled .cpp file which contains the function definitions. This can hide the implementation and prevent tampering. The user would have to define every function again to change the implementation. Functions within classes can access and modify (unless the function is constant) data members without declaring them, because the data members are already declared in the class.

Simple example:

// file: Foo.h
// the header file named the same as the class helps locate classes within a project
// one class per header file makes it easier to keep the header file readable (some classes can become large)
// each programmer should determine what style works for them or what programming standards their
// teach/professor/employer has

#ifndef FOO_H
#define FOO_H

class Foo {
public:
    Foo();                  // function called the default constructor
    Foo( int a, int b );    // function called the overloaded constructor
    int Manipulate( int g, int h );
private:
    int x;
    int y;
};

#endif
// file: Foo.cpp
#include "Foo.h"

Foo::Foo() {
    x = 5;
    y = 10;
}

Foo::Foo( int a, int b ){
    x = a;
    y = b;
}
int Foo::Manipulate( int g, int h ){
    x = h + g*x;
    y = g + h*y;
}

[edit] this pointer

this is a C++ keyword. The this pointer acts like any other pointer. Read the section concerning pointers and references to understand more about what a pointer does. The this pointer is only accessible within nonstatic member functions of a class (or a union or struct). The this pointer is not available in static member functions. It is not necessary for the programmer to write code for the this pointer as the compiler does this implicitly. When you debug code in a GUI compiler with some way of viewing the current variables and when the program steps into nonstatic class functions, you can see the this pointer in some variable list.

In the following example, the compiler inserts an implicit parameter this in the nonstatic member function int getData(). Additionally, the code initiating the call passes an implicit parameter (provided by the compiler).

class Foo
{
private:
    int x;
public:
    Foo(){  x=5;  };
    int getData(this)      // this is provided by the compiler at compile time
    {   
        return this->x;  
    };
};

int main()
{
    Foo Example;
    int temp;

    temp = Example.getData(&Example);  // compiler adds the &Example reference at compile time
    return 0;
}

There are certain times when a programmer should know about and use the this pointer. The this pointer should be used when overloading the assignment operator to prevent a catastrophe. For example, add in an assignment operator to the code above.

class Foo
{
private:
    int x;
public:
    Foo(){  x=5;  };
    int getData()          
    {                            
        return x;  
    };
    Foo& operator=( const Foo &RHS );
};

Foo& Foo::operator=( const Foo &RHS )
{
    if ( this != &RHS ) {    // the if this test prevents an object from copying to itself (ie. RHS = RHS;)
        this->x = RHS.x;        // this is suitable for this class, but can be more complex when
                                // copying an object in a different much larger class
    }

    return ( *this );       // returning an object allows chaining, like  a = b = c; statements
}

However little you may know about this, the pointer is important in implementing any class.

[edit] const member function

This type of member function cannot modify the non-mutable member variables of a class. It's a hint both to the programmer and the compiler that a given member function doesn't change the internal state of a class.

Take for example:

class Foo
{
 public:
   int value() const
   {
     return m_value;
   }

   void setValue( int i )
   {
     m_value = i;
   }

 private:
   int m_value;
};

Here value() clearly does not change m_value and as such can and should be const. However setValue() does modify m_value and as such cannot be const.

Another subtlety often missed is a const member function cannot call a non-const member function (and the compiler will complain if you try). The const member function cannot change member variables and a non-const member functions can change member variables. Since we assume non-const member functions do change member variables, const member functions are assumed to never change member variables and can't call functions that do change member variables.

The following code example explains what const can do depending on where it is placed.

class Foo
{
public:
   /*
    * Modifies m_widget and the user
    * may modify the returned widget.
    */
   Widget *widget();

   /*
    * Does not modify m_widget but the
    * user may modify the returned widget.
    */
   Widget *widget() const;

   /*
    * Modifies m_widget, but the user
    * may not modify the returned widget.
    */
   const Widget *cWidget();

   /*
    * Does not modify m_widget and the user
    * may not modify the returned widget.
    */
   const Widget *cWidget() const;

private:
   Widget *m_widget;
};

[edit] Accessors and Modifiers (Getter/Setter)

What is an accessor?
An accessor is a member function that does not modify the state of an object. The accessor functions should be declared as const operations.
What is a modifier?
A modifier, also called a modifying function, is a member function that changes the value of at least one data member. In other words, an operation that modifies the state of an object. Modifiers are also known as ‘mutators’.

Getter is another common definition of an accessor due to the naming ( GetSize() ) of that type of member functions.

Setter is another common definition of an modifier due to the naming ( SetSize( int a_Size ) ) of that type of member functions.

[edit] Lazy initialization

It is always needed to maintain the balance between the performance of the system and the resource consumption. Let us see how to reduce the resource consumption until it is required.

Lazy instantiation is one of the memory conservation mechanisms, by which the object initialization is deferred until it is required.

Look at the following example:

class Wheel {
       int speed;
   public:
       int getSpeed(){
           return speed;
       }
       void setSpeed(int speed){
           this->speed = speed;
       }
};
//this must be corrected
class Car{
     private Wheel wheel = new Wheel();
     public int getCarSpeed(){
        return wheel.getSpeed();
     }
     public String getName(){
        return "My Car is a Super fast car";
     }
     public static void main(String a[]){
       Car myCar = new Car();
       System.out.println(myCar.getName());
     }
}

Instantiation of class Car by default instantiates the Class Wheel. The purpose of the whole class is to just print the name of the car. Here the instance wheel doesn't serve any purpose, loading of which is a complete resource waste.

It is better to defer the instantiation of the un-required class until it is needed. Modify the above class Car as follows:

//must be corrected
class Car {
     private Wheel wheel;
     public int getCarSpeed(){
        if(wheel == null){
           wheel = new Wheel();
        }
        return wheel.getSpeed();
     }
     public String getName(){
        return "My Car is a Super fast car";
     }
     public static void main(String a[]){
       Car myCar = new Car();
       System.out.println(myCar.getName());
     }
}

Now the Wheel will be instantiated only, when the member function getCarSpeed() is called. This is known as Lazy initialization.

[edit] Overloading

Member functions can be overloaded. This means that multiple member functions can exist with the same name, but must have different signatures. A member function's signature is comprised of the member function's name and the type and order of the member function's parameters.

Due to name hiding, if a member in the derived class shares the same name with members of the base class, they will be hidden to the compiler. To make those members visible, one can use declarations to introduce them from base class scopes.

[edit] Constructor

A constructor is a special member function used to create new objects. The compiler calls the constructor after the new object has been allocated in memory, and converts that "raw" memory into a proper, typed object. Additionally, constructors are responsible for defining data members upon object instantiation (when an object is declared), if the programmer chooses.

Example when declaring an object of type class Foo (Foo defined in "Member Functions" above):

Foo myTest;                 // essentially what happens is:  Foo myTest();
Foo myTest( 3, 54 );        // accessing the overloaded constructor
Foo myTest = Foo( 20, 45 ); // although a new object is created, there are some extra function calls involved
                            // with more complex classes, an assignment operator should
                            // be defined to ensure a proper copy (includes deep copy)
                            // myTest would be constructed with the default constructor, and then the
                            // assignment operator copies the unnamed Foo( 20, 45 ) object to myTest

using new with a constructor

Foo* myTest = new Foo();           // this defines a pointer to a dynamically allocated object
Foo* myTest = new Foo( 40, 34 );   // constructed with Foo( 40, 34 )
// be sure to use delete to avoid memory leaks

NOTE:

While there is no risk in using new to create an object, it is often best to avoid using memory allocation functions within objects' constructors. Specifically, using new to create an array of objects, each of which also uses new to allocate memory during its construction, often results in runtime errors. If a class or structure contains members which must be pointed at dynamically created objects, it is best to sequentially initialize these arrays of the parent object, rather than leaving the task to their constructors.
This is especially important when writing code with exceptions, if an exception is thrown before a constructor is completed, the associated destructor will not be called for that object, see more about it in C++ Programming/Exception Handling.

A constructor can't delegate to another. It is also considered desirable to reduce the use of default arguments, if a maintainer has to write and maintain multiple constructors it can result in code duplication, which reduces maintainability because of the potential for introducing inconsistencies and even lead to code bloat.

Default Constructors

A default constructor is one which can be called with no arguments. Most commonly, a default constructor is declared without any parameters, but it is also possible for a constructor with parameters to be a default constructor if all of those parameters are given default values.

In order to create an array of objects of a class type, the class must have an accessible default constructor; C++ has no syntax to specify constructor arguments for array elements.

[edit] Overloaded Constructors

When an object of a class is instantiated, the class writer can provide various constructors each with a different purpose. A large class would have many data members, some of which may or may not be defined when an object is instantiated. Anyway, each project will vary, so a programmer should investigate various possibilities when providing constructors.

These are all constructors for a class myFoo.

 
myFoo(); // default constructor, the user has no control over initial values
         // overloaded constructors
 
myFoo( int a, int b=0 ); // allows construction with a certain 'a' value, but accepts 'b' as 0
                         // or allows the user to provide both 'a' and 'b' values
 // or
 
myFoo( int a, int b ); // overloaded constructor, the user must specify both values
 
class myFoo {
private:
  int Useful1;
  int Useful2;
 
public:
  myFoo(){                     // default constructor
           Useful1 = 5;
           Useful2 = 10;  
          };
 
  myFoo( int a, int b = 0 ) { // two possible cases when invoked
         Useful1 = a;
         Useful2 = b;
  };
 
};
 
myFoo Find;           // default constructor, private member values Useful1 = 5, Useful2 = 10
myFoo Find( 8 );      // overloaded constructor case 1, private member values Useful1 = 8, Useful2 = 0
myFoo Find( 8, 256 ); // overloaded constructor case 2, private member values Useful1 = 8, Useful2 = 256
[edit] Constructor initialization lists

Constructor initialization lists (or member initialization list) is probably the only way to initialize data members. Constructors for the members are included between the argument list and the body of the constructor (separated from the argument list by a colon).

Example:

MyClass::MyClass(int mySimpleMember, MyComplexClass myComplexMember)
: _myComplexMember(myComplexMember)
{
 _mySimpleMember=mySimpleMember;
}

This is more efficient than assigning value to the complex data member inside the body of the constructor because in that case the variable is initialized with its corresponding constructor.

Note that the arguments provided to the constructors of the members do not need to be arguments to the constructor of the class; they can also be constants. Therefore you can create a default constructor for a class containing a member with no default constructor.

Example:

MyClass::MyClass() : _myComplexMember(0) { }

It is useful to initialize your members in the constructor using this initialization lists. This makes it obvious for the reader that the constructor does not execute logic. The order the initialization is done should be the same as you defined your base-classes and members. Otherwise you can get warnings at compile-time. Once you start initializing your members make sure to keep all in the constructor(s) to avoid confusion and possible 0xbaadfood.

It is safe to use constructor parameters that are named like members.

Example:

class MyClass : public MyBaseClassA, public MyBaseClassB {
  private:
    int c;
    void *pointerMember;
  public:
    MyClass(int,int,int);
};
/*...*/
MyClass::MyClass(int a, int b, int c):
 MyBaseClassA(a)
,MyBaseClassB(b)
,c(c)
,pointerMember(NULL)
,referenceMember()
{
 //logic
}

Note that this technique was also possible for normal functions but it is now obsoleted and is classified as an error in such case.

NOTE:
It is a common misunderstanding that initialization of data members can be done within the body of constructors. All such kind of so-called "initialization" are actually assignments. The C++ standard defines that all initialization of data members are done before entering the body of constructors. The simplest way to attain this is using constructor initialization lists.

[edit] Virtual constructor

There is a hierarchy of classes with base class Foo. Given an object bar belonging in the hierarchy, it is desired to be able to do the following:

  1. Create an object baz of the same class as bar (say, class Bar) initialized using the default constructor of the class. The syntax normally used is:
    Bar* baz = bar.create();
  2. Create an object baz of the same class as bar which is a copy of bar. The syntax normally used is:
    Bar* baz = bar.clone();

In the class Foo, the methods Foo::create() and Foo::clone() are declared as follows:

class Foo
{
    ...

    public:
        // Virtual default constructor
        virtual Foo* create() const;

        // Virtual copy constructor
        virtual Foo* clone() const;
};

If Foo is to be used as an abstract class, the functions may be made pure virtual:

class Foo
{
    ...

    public:
        virtual Foo* create() const = 0;
        virtual Foo* clone() const = 0;
};

In order to support the creation of a default-initialized object, and the creation of a copy object, each class Bar in the hierarchy must have public default and copy constructors. The virtual constructors of Bar are defined as follows:

class Bar : ... // Bar is a descendant of Foo
{
    ...

    public:
    // Non-virtual default constructor
    Bar ();
    // Non-virtual copy constructor
    Bar (const Bar&);

    // Virtual default constructor, inline implementation
    Bar* create() const { return new Foo (); }
    // Virtual copy constructor, inline implementation
    Bar* clone() const { return new Foo (*this); }
};

The above code uses Covariant return types. If your compiler doesn't support Bar* Bar::create(), use Foo* Bar::create() instead, and similarly for clone().

While using these virtual constructors, you must manually deallocate the object created by calling delete baz;. This hassle could be avoided if a smart pointer (e.g. std::auto_ptr<Foo>) is used in the return type instead of the plain old Foo*.

Remember that whether or not Foo uses dynamically allocated memory, you must define the destructor virtual ~Foo () and make it virtual to take care of deallocation of objects using pointers to an ancestral type.

[edit] Destructor

Destructors are crucial in avoiding resource leaks and in implementing the RAII idiom. Resources which are allocated in a Constructor of a class are usually released in the destructor of that class. If an object of a derived type is destructed, first the destructor of the most derived object is executed. Then member objects and base class subjects are destructed recursively, in the reverse order their corresponding constructors completed.

The dynamic type of the object will change from the most derived type as destructors run, symmetrically to how it changes as constructors execute. This affects the functions called by virtual calls during construction and destruction, and leads to the common (and reasonable) advice to avoid calling virtual functions of an object either directly or indirectly from its constructors or destructors.

[edit] Static Members

Member functions or variables declared static are shared between all instances of an object type. This means that only one copy of the member function or variable exists for any object type. Named constructors are a good example of using static member functions. Named constructors is the name given to functions used to create an object of a class C without (directly) using its constructors. This might be used for the following:

  1. To circumvent the restriction that constructors can be overloaded only if their signatures differ.
  2. Making the class non-inheritable by making the constructors private.
  3. Preventing stack allocation by making constructors private

Declare a static member function that uses a private constructor to create the object and return it. (It could also return a pointer or a reference but this complication seems useless, and turns this into the factory pattern rather than a conventional named constructor.) Here's an example for a class that stores a temperature that can be specified in any of the different temperature scales.

class Temperature
{
    public:
        static Temperature Fahrenheit (double f);
        static Temperature Celsius (double c);
        static Temperature Kelvin (double k);
    private:
        Temperature (double temp);
        double _temp;
};

Temperature::Temperature (double temp):_temp (temp) {}

Temperature Temperature::Fahrenheit (double f)
{
    return Temperature ((f + 459.67) / 1.8);
}

Temperature Temperature::Celsius (double c)
{
    return Temperature (c + 273.15);
}

Temperature Temperature::Kelvin (double k)
{
    return Temperature (k);
}

[edit] Container class

A class that is used to hold objects in memory or external storage is often called a container class. A container class acts as a generic holder and has a predefined behavior and a well-known interface. It is also a supporting class whose purpose is to hide the topology used for maintaining the list of objects in memory. When it contains a group of mixed objects, the container is called a heterogeneous container; when the container is holding a group of objects that are all the same, the container is called a homogeneous container.

[edit] Ensuring objects of a class are never copied

This is required e.g. to prevent memory-related problems that would result in case the default copy-constructor or the default assignment operator is unintentionally applied to a class C which uses dynamically allocated memory, where a copy-constructor and an assignment operator are probably an overkill as they won't be used frequently.

Some style guidelines suggest making all classes non-copyable by default, and only enabling copying if it makes sense. Other (bad) guidelines say that you should always explicitly write the copy constructor and copy assignment operators; that's actually a bad idea, as it adds to the maintenance effort, adds to the work to read a class, is more likely to introduce errors than using the implicitly declared ones, and doesn't make sense for most object types. A sensible guideline is to think about whether copying makes sense for a type; if it does, then first prefer to arrange that the compiler-generated copy operations will do the right thing (e.g., by holding all resources via resource management classes rather than via raw pointers or handles), and if that's not reasonable then obey the law of three. If copying doesn't make sense, you can disallow it in either of two idiomatic ways as shown below.

Just declare the copy-constructor and assignment operator, and make them private. Do not define them. As they are not protected or public, they are inaccessible outside the class. Using them within the class would give a linker error since they are not defined.

class C
{
    ...

    private:
        // Not defined anywhere
        C (const C&);
        C& operator= (const C&);
};

Remember that if the class uses dynamically allocated memory for data members, you must define the memory release procedures in destructor ~C () to release the allocated memory.

A class which only declares these two functions can be used as a private base class, so that all classes which privately inherits such a class will disallow copying. One of the Boost libraries, contains the boost:noncopyable utility class which is a practical example. Below shows an example of using this class:

class C : boost::noncopyable
{
     ...
};