This post is also available in: en, en and en

Creating Robust Classes

In chapter three we coded a simple class in C++ using the syntax that we had introduced in that chapter. Of course, it was not possible (or even diserable) to discuss all possible 'syntax avenues' because doing so would be confusing. In this chapter we wish to create more robust and efficient classes and we realise this goal by using some of the syntax that C++ offers. In particular, we address a number of issues that have to do with data and object security, such as:

Issue 1: Ensuring that objects and their data are created in a safe way.

Issue 2: Accesing and using objects in a safe way; avoiding side-effects.

Issue 3: Working with object references rather than copies of objects.

Issue 4: Optimization: static objects and static member data.

This is quite a lot of territory to cover and the results in this chapter will be used again and again throughout this book. Thus, this chapter is a vital link to future chapters.

The most important topics are:

  • Passing parameters to functions by value or by reference.
  • Function overloading: ability to define several functions having the same name.
  • More on constructors.
  • Not all functions need be member functions: non-member functions.
  • The const keyword and its consequences for C++ applications.

After having understood these topics and having implemented them in simple classes the reader will have reached a level of expertise approaching yellow belt. We discuss these topics not only because they are supported in C++ but because they help us become good C++ developers. They also promote the reliability and efficiency of our code.

Call by reference and call by value

In C++ one can create functions taking zero or more arguments in their parameter list. To this end, we need discuss in what forms these arguments are created and used in a function. We take a simple example to motivate what we mean. Let us consider a function that calculates the larger of two numbers:

In [2]:
double Max(double x, double y)
{
    if (x > y)
        return x;
    return y;
}

This is a very simple function of course and in this case we say that the inputs parameters x and y are used in a call-by-value manner; this means that copies of these variables are made on the stack when the function is called:

In [3]:
#include <iostream>
{
    double d1 = 1.0;
    double d2 = -34536.00;
    
    // Copies of d1 and d2 offered to the function Max()
    double result = Max(d1, d2);
    std::cout << "Maxvalue is " << result << std::endl;
}
input_line_11:8:33: error: use of overloaded operator '<<' is ambiguous (with operand types 'basic_ostream<char, std::char_traits<char> >' and 'double')
    std::cout << "Maxvalue is " << result << std::endl;
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^  ~~~~~~
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:166:7: note: candidate function
      operator<<(long __n)
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:170:7: note: candidate function
      operator<<(unsigned long __n)
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:174:7: note: candidate function
      operator<<(bool __n)
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:178:7: note: candidate function
      operator<<(short __n);
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:181:7: note: candidate function
      operator<<(unsigned short __n)
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:189:7: note: candidate function
      operator<<(int __n);
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:192:7: note: candidate function
      operator<<(unsigned int __n)
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:201:7: note: candidate function
      operator<<(long long __n)
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:205:7: note: candidate function
      operator<<(unsigned long long __n)
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:224:7: note: candidate function
      operator<<(float __f)
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:232:7: note: candidate function
      operator<<(long double __f)
      ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:508:5: note: candidate function [with _Traits = std::char_traits<char>]
    operator<<(basic_ostream<char, _Traits>& __out, char __c)
    ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:502:5: note: candidate function [with _CharT = char, _Traits = std::char_traits<char>]
    operator<<(basic_ostream<_CharT, _Traits>& __out, char __c)
    ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:514:5: note: candidate function [with _Traits = std::char_traits<char>]
    operator<<(basic_ostream<char, _Traits>& __out, signed char __c)
    ^
/home/carlosal1015/.conda/envs/cling/bin/../lib/gcc/x86_64-conda_cos6-linux-gnu/7.3.0/../../../../x86_64-conda_cos6-linux-gnu/include/c++/7.3.0/ostream:519:5: note: candidate function [with _Traits = std::char_traits<char>]
    operator<<(basic_ostream<char, _Traits>& __out, unsigned char __c)
    ^
Interpreter Error: 

In this case we work with copies of d1 and d2 in the body of Max() and not d1 and d2 themselves. This process is taken care of automatically and you do not have to worry about this as programmer.

The call-by-value technique is also applicable, not only to built-in-types as we have just seen but also to class instances (objects). This means that objects (even big ones) will be copied if they are used in this call-by-value way. Let us take an example of a class having an embedded fixed-size array as member data:

In [ ]:
class SampleClass
{
public: // 
}
In [1]:
!cat Point.hpp
// Point.hpp
//
// Header file for Points in two dimensions. A given Point has 3 coordinates
// for compatibility with other systems. However, it is not possible to
// influence the third coordinate and furthermore, the delivered functionality
// is typically two-dimensional.
//
// (C) Copyright Datasim BV 1995 - 2005

#ifndef Point_HPP
#define Point_HPP

#include <iostream>

class Shape
{
};

class Point : public Shape
{
private:
	double x;	// X coordinate
	double y;	// Y coordinate

	void init(double dx, double dy);

public:
	// Constructors
	Point();								// Default constructor
	Point(double xval, double yval);		// Initialize with x and y value
	Point(const Point& pt);					// Copy constructor
	~Point();								// Destructor

	// Accessing functions
	double X() const;					// The x-coordinate
	double Y() const;					// The y-coordinate

	// Modifiers
	void X(double NewX);				
	void Y(double NewY);

	// Arithmetic functions
	Point add(const Point& p) const;		// Return current + p
	Point subtract(const Point& p) const;	// Return current - p
	Point scale(const Point& pt) const;		// Return current * p
	Point MidPoint(const Point& pt) const;		// Point midway


	// Copy
	Point& copy(const Point& p);			// Copy p in current

};

	std::ostream& operator << (std::ostream& os, const Point& p);

#endif // Point_HXX

In [2]:
!cat Point.cpp
// Point.cpp
//
// Header file for Points in two dimensions. A given Point has 3 coordinates
// for compatibility with other systems. However, it is not possible to
// influence the third coordinate and furthermore, the delivered functionality
// is typically two-dimensional.
//
// Last Modifications:
// chap04 AM Kick off
// chap05 AM Changed using const and reference
// chap05 AM Added copy constructor
// 2005-5-20 DD modified POINT -> Point
//
// (C) Copyright Datasim BV 1995 - 2006

#include "Point.hpp"

// Private functions
void Point::init(double dx, double dy)
{
	x = dx;
	y = dy;
}

Point::Point()
{// Default constructor
	init(0.0, 0.0);
}

Point::Point(double newx, double newy)
{// Initialize using newx and newy
	init(newx, newy);
}

/*
Point::Point(double newx, double newy) : x(newx), y(newy)
{// Initialize using newx and newy

	// init(newx, newy); NOT NEEDED
}
*/

Point::Point(const Point& pt)
{// Copy constructor
	x = pt.x;
	y = pt.y;
}

Point::~Point()
{
}

double Point::X() const
{
	return x;
}

double Point::Y() const
{
	return y;
}

// Modifiers
void Point::X(double NewX)
{
	x = NewX;
}

void Point::Y(double NewY)
{
	y = NewY;
}

// Arithmetic functions
Point Point::add(const Point& p) const
{
	return Point(x + p.x, y + p.y);
}

Point Point::subtract(const Point& p) const
{
	return Point(x - p.x, y - p.y);
}

Point Point::scale(const Point& p) const
{ // Scale a Point by another Point
	return Point(x * p.x, y * p.y);
}

Point Point::MidPoint(const Point& p2) const
{ // Scale a Point by another Point

	Point result((x + p2.x) * 0.5, (y + p2.y) * 0.5);

	return result;
}


// Copy
Point& Point::copy(const Point& p)
{// Copy p in current
	x = p.x;
	y = p.y;

	return *this;
}

// Output
std::ostream& operator << (std::ostream& os, const Point& p)
{ // Output to screen

	os << "Point: (" << p.X() << ", " << p.Y() << ")" << std::endl;

	return os;

}
In [3]:
!cat TestPoint.cpp
// Testpoint.cpp
//
// Simple examples of using Point class.
//
// (C) Datasim Education BV 2006

#include "Point.hpp"

int main()
{

	// Create a point
	Point p1;

	// Modify it cordinates
	p1.X(2.0);
	p1.Y(3.0);

	// Print it
	std::cout << "[" << p1.X() << "," << p1.Y() << "]" << std::endl;

	// Midpoint
	Point pL(0.0, 0.0);
	Point pR(1.0, 1.0);

	std::cout << pL; std::cout << pR;

	Point PM = pL.MidPoint(pR);
	std::cout << "Midpoint " << PM << std::endl;

	return 0;
}

Published

Last Updated

Category

c++

Tags

Contact