«

»

Feb 19

Organising Classes

Share

This tutorial explains how classes should be organised in separate files and what the benefits of this are. The Vector class from the previous tutorial is used, if you haven’t read it you should go there first: link.

Introduction

As discussed in the previous tutorials classes are a very useful part of programming in C++. One of the biggest advantages of using classes is to create code that can be efficiently reused.

This tutorial explains how classes should be organised to allow them to be reused properly.

The Basic Layout

What We Have So Far

After the previous tutorial, you should have something that looks roughly like the following code. This is what we will split into separate files.

#include <iostream>
#include <cmath>
 
using namespace std;
 
class Vector
{
	public:
		Vector()
		{
			Vector( 0.0f, 0.0f );
		}
 
		Vector( float x, float y )
		{
			Set( x, y );
		}
 
		void Set( float x, float y )
		{
			m_x = x;
			m_y = y;
		}
 
		void SetX( float x )
		{
			m_x = x;
		}
 
		void SetY( float y )
		{
			m_y = y;
		}
 
		float GetX()
		{
			return m_x;
		}
 
		float GetY()
		{
			return m_y;
		}
 
		void Zero()
		{
			Set( 0.0f, 0.0f );
		}
 
		float Length()
		{
			// Return the square root of the product of the two components squared
			// Note: I use pow( number, 2 ) rather than (number*number) because I prefer it aesthetically
			return sqrt( pow( m_x, 2 ) + pow( m_y, 2 ) );
		}
 
		void Normalise()
		{
			float length = Length();
			if ( length != 0 ) // Ensure the length is not 0 before continuing
			{
				// Divide each component by the length
				m_x /= length;
				m_y /= length;
			}
		}
 
		float Dot( const Vector& w )
		{
			return ((m_x * w.m_x) + (m_y * w.m_y));
		}
 
	private:
		float m_x;
		float m_y;
 
	// Declare the operator functions as friends
	friend Vector operator+(const Vector& lhs, const Vector& rhs);
	friend Vector operator-(const Vector& lhs, const Vector& rhs);
	friend Vector operator*(const Vector& lhs, const Vector& rhs);
	friend Vector operator/(const Vector& lhs, const Vector& rhs);
	friend Vector operator*(const Vector& lhs, const float& rhs);	
	friend Vector operator/(const Vector& lhs, const float& rhs);
	friend bool operator==(const Vector& lhs, const Vector& rhs);
};
 
Vector operator+(const Vector& lhs, const Vector& rhs)
{
	return Vector(lhs.m_x + rhs.m_x, lhs.m_y + rhs.m_y);
}
 
Vector operator-(const Vector& lhs, const Vector& rhs)
{
	return Vector(lhs.m_x - rhs.m_x, lhs.m_y - rhs.m_y);
}
 
Vector operator/(const Vector& lhs, const Vector& rhs)
{
	return Vector(lhs.m_x / rhs.m_x, lhs.m_y / rhs.m_y);
}
 
Vector operator*(const Vector& lhs, const Vector& rhs)
{
	return Vector(lhs.m_x * rhs.m_x, lhs.m_y * rhs.m_y);
}
 
Vector operator*(const Vector& lhs, const float& rhs)
{
	return Vector(lhs.m_x*rhs, lhs.m_y*rhs);
}
 
Vector operator/(const Vector& lhs, const float& rhs)
{
	return Vector(lhs.m_x/rhs, lhs.m_y/rhs);
}
 
bool operator==( const Vector& lhs, const Vector& rhs )
{
	return ( ( lhs.m_x == rhs.m_x ) && ( lhs.m_y == lhs.m_y ) );
}
 
int main( int argc, char* argv[] )
{
 
	// Add the rest of your code here!
 
	return 0;
}

Splitting the Code

Header

The first thing to do is create a header file. A header file usually contains the declarations of the code to include, which in our case is the class but functions, structures, types, variables, enums, and pretty much anything else can be declared in a header file.

The most common method of naming header files is to name them after the class or function they contain. So, create a file called Vector.h and put the following code in it.

#include <iostream>
#include <cmath>
 
class Vector
{
	public:
		Vector();
		Vector( float x, float y );
 
		void Set( float x, float y );
		void SetX( float x );
		void SetY( float y );
 
		float GetX();
		float GetY();
 
		void Zero();
 
		float Length();
 
		void Normalise();
 
		float Dot( const Vector& w );
 
	private:
		float m_x;
		float m_y;
 
	// Declare the operator functions as friends
	friend Vector operator+(const Vector& lhs, const Vector& rhs);
	friend Vector operator-(const Vector& lhs, const Vector& rhs);
	friend Vector operator*(const Vector& lhs, const Vector& rhs);
	friend Vector operator/(const Vector& lhs, const Vector& rhs);
	friend Vector operator*(const Vector& lhs, const float& rhs);	
	friend Vector operator/(const Vector& lhs, const float& rhs);
	friend bool operator==(const Vector& lhs, const Vector& rhs);
};

As you can see, only the declarations for the class are in this file, none of the actual function bodies are here.
It’s also possible to change things like void Set( float x, float y ); to void Set( float, float ); as the declaration only needs to know the types of the parameters. However, including parameter names often helps to show what they are used for, which can be very helpful when reading through the header code, so it’s often a good idea to write them.

Source

The second file is known as the source file. This file will contain all the actual function bodies and impementation. The common convention for source files is the same as headers, to name them after the class or function they contain. So create a file called Vector.cpp and add the following code.

// Include the header file for the class
#include "Vector.h"
 
Vector::Vector()
{
	Vector( 0.0f, 0.0f );
}
 
Vector::Vector( float x, float y )
{
	Set( x, y );
}
 
void Vector::Set( float x, float y )
{
	m_x = x;
	m_y = y;
}
 
void Vector::SetX( float x )
{
	m_x = x;
}
 
void Vector::SetY( float y )
{
	m_y = y;
}
 
float Vector::GetX()
{
	return m_x;
}
 
float Vector::GetY()
{
	return m_y;
}
 
void Vector::Zero()
{
	Set( 0.0f, 0.0f );
}
 
float Vector::Length()
{
	// Return the square root of the product of the two components squared
	// Note: I use pow( number, 2 ) rather than (number*number) because I prefer it aesthetically
	return sqrt( pow( m_x, 2 ) + pow( m_y, 2 ) );
}
 
void Vector::Normalise()
{
	float length = Length();
	if ( length != 0 ) // Ensure the length is not 0 before continuing
	{
		// Divide each component by the length
		m_x /= length;
		m_y /= length;
	}
}
 
float Vector::Dot( const Vector& w )
{
	return ((m_x * w.m_x) + (m_y * w.m_y));
}
 
Vector operator+(const Vector& lhs, const Vector& rhs)
{
	return Vector(lhs.m_x + rhs.m_x, lhs.m_y + rhs.m_y);
}
 
Vector operator-(const Vector& lhs, const Vector& rhs)
{
	return Vector(lhs.m_x - rhs.m_x, lhs.m_y - rhs.m_y);
}
 
Vector operator/(const Vector& lhs, const Vector& rhs)
{
	return Vector(lhs.m_x / rhs.m_x, lhs.m_y / rhs.m_y);
}
 
Vector operator*(const Vector& lhs, const Vector& rhs)
{
	return Vector(lhs.m_x * rhs.m_x, lhs.m_y * rhs.m_y);
}
 
Vector operator*(const Vector& lhs, const float& rhs)
{
	return Vector(lhs.m_x*rhs, lhs.m_y*rhs);
}
 
Vector operator/(const Vector& lhs, const float& rhs)
{
	return Vector(lhs.m_x/rhs, lhs.m_y/rhs);
}
 
bool operator==( const Vector& lhs, const Vector& rhs )
{
	return ( ( lhs.m_x == rhs.m_x ) && ( lhs.m_y == lhs.m_y ) );
}

As you can see, all the functions are prefixed with Vector:: because that’s the name of our class, and :: is the scope resolution operator.
It’s also important to include the header file for the class, otherwise the compiler will have no idea what the functions are for.

Now all you need to do is include the header file in your main.cpp using the simple #include "Vector.h" and you will be able to use the Vector class just as you did before.

Tidying Up

There are still some potential issues that could occur. For example, if the Vector.h is included in another header for a different class, and then both classes are included in the main code, the compiler will complain that the Vector class is already defined and will error. This must be fixed by adding something called an inclusion guard (or sometimes include guard) which simply prevents the header from being included more than once. It’s very simple to do, simple add the following at the very beginning of the header file.

#ifndef _VECTOR_H_
#define _VECTOR_H_

This tells the compiler to check if _VECTOR_H_ is defined. If it isn’t, the compiler will define it and then the code for our class is included. If not, the compiler will skip the code, which prevents it from being included more than once. _VECTOR_H_ is simply a name we have given to the variable, we could just have easily have written #ifndef DaNcIng_Un1c0rn .... The common convention, however, is to use the filename, with underscores at either side, and one replacing the dot in the filename. So MyClass.h would have #ifndef _MYCLASS_H_ ... and so on.

You must also add the following line to the very end of your class file, just to show the compiler where the #if ends.

#endif

The entire header should look something like the following.

#ifndef _VECTOR_H_
#define _VECTOR_H_
 
#include <iostream>
#include <cmath>
 
class Vector
{
	public:
		Vector();
		Vector( float x, float y );
 
		void Set( float x, float y );
		void SetX( float x );
		void SetY( float y );
 
		float GetX();
		float GetY();
 
		void Zero();
 
		float Length();
 
		void Normalise();
 
		float Dot( const Vector& w );
 
	private:
		float m_x;
		float m_y;
 
	// Declare the operator functions as friends
	friend Vector operator+(const Vector& lhs, const Vector& rhs);
	friend Vector operator-(const Vector& lhs, const Vector& rhs);
	friend Vector operator*(const Vector& lhs, const Vector& rhs);
	friend Vector operator/(const Vector& lhs, const Vector& rhs);
	friend Vector operator*(const Vector& lhs, const float& rhs);	
	friend Vector operator/(const Vector& lhs, const float& rhs);
	friend bool operator==(const Vector& lhs, const Vector& rhs);
};
 
#endif

What’s the Point?

You may be thinking now, what’s the point of doing all this? To put it simply, it reduces compile time.

Organising the code in this way allows for only the information that is completely necessary for the compiler to be included, which is the prototype of the class. This allows the compiler to perform its task of checking that all variables are of the correct type, that all parameters are present, etc. without having to compile the entire body of the class every time.

By simply having less to compile, the time taken is reduced. There are some more advanced advantages and disadvantages to using separate source and header files, but they require some understanding of other more advanced topics so aren’t really relevant here.

Congratulations!

If you’ve followed the class tutorials up to here, you should now know enough about them to start using your own classes in C++.

As usual, any comments or questions either post a comment below or send me an email.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>