lass is the principal component in Object Oriented Programming (OOP) C++; this article provides a brief introduction to classes and their principal features (access specifiers, encapsulation,constructor, destructor, copy constructor, copy assignment operator); we also cover some concepts related with classes such as cascading member function and the explicit keyword.

Definition

C++ class and struct are defined as structured chunks of memory that can store a heterogeneous set of data. Their principal features are data membersmember functionand access specifiers.

The only difference between class and struct, it’s based on the fact that, class members are private by default, while struct members are public. For illustrate the last point, here is the next snippet:

/*
  Class
*/

class Fraction{
//These members are private
  int m_nNumerator;
  int m_nDenominator

private:
//access specifiers (as above) define the visibility of class' members
//Private members are only visible in the class definition 
//(class' functions)

  string m_sName;

public:
  //Public members are visible in the class definition, and
  //wherever a class' instance is defined.
  void toString();
  double toDouble()const;

}

/*
  Struct
*/

struct Byte{
//These members are public
  bool m_bMSB;
  bool m_bLSB;

private:
//...

public:

  Byte& leftShift(int times);
  Byte& rightShift(int times);
}

We’d defined a Fraction class and a Byte struct for demonstration purpose, where you can see the public and private words; these words are called access specifiers. Access specifiers determine the grade of visibility/availability of class members, there are three of them: public, protected and private:

  • A public member can be accessed anywhere that includes the class definition file, and if an object class instance is in scope (public static members in the class definition, do not need instance).
  • protected member can be accessed inside the class definition or inside the its derived class definitions.
  • private member is only available to definition class member function, and class friends.

Class Declaration and Definition

The class declaration is the design of the class where we’re declaring variables and prototyping function. Usually, class declaration are placed in a header file (.h), like the next snippet illustrates:

#ifndef FRACTION_H //If a header with the same name is defined already,
// skip this definition
#define FRACTION_H

#include <string> //Qt Header, don't pay much attention

/*
class ClassName {
public:
  publicMembers
private:
  privateMembers
};
*/

class Fraction{

//implementation functions
public:
  void set(int numerator,int denominator);
  Fraction();
  Fraction(const double d);
  Fraction(const int num =1,const int dem =1);
  string toString()const;
  double toDouble()const;
  //...

//data members
private:
  int m_nNumerator,m_nDenominator;
 
};

#endif

The class definition (or implementation) is where we can develop the class functionality. Here we’ll set “what the class does” based on function and variables that were declared in the class declaration. Any definition outside the class requires a scope resolution operator (ClassName::) before its name. To demonstrate this, the next code display the implementation of the Fraction class which use the scope resolution operator:

double Fraction::toDouble(){
 
  return 1.0*(m_nNumerator/m_nDenominator);
}

//Don't you remember the const reference used as function parameter?
//Check past articles
//Two fraction addition
Fraction& Fraction::add(const Fraction & other){

  Fraction tempFraction;
  if(m_nDenominator==other.m_nDenominator){
 
    tempFraction.m_nNumerator=m_nNumerator+other.m_nNumerator;
    tempFraction.m_nDenominator=m_nDenominator;
  }
 
  else{  
    tempFraction.m_nNumerator=(m_nNumerator*other.m_nDenominator)+(m_nDenominator*other.m_nNumerator);
    tempFraction.m_nDenominator=m_nDenominator*other.m_nDenominator;
  }
 
  return simplify(tempFraction);
}

//We wish to change f inside the function-so isn't a const reference
//in constrast than the last function
//This function is awesome!!

Fraction& Fraction::simplify(Fraction& f){

//They may be static member also... but we're using them only here. 
  const int sizeArray=46;
  const int aPrimes[]={ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
                        43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
                        101, 103, 107, 109, 113, 127, 131, 137, 139, 149,
                        151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199 };
 
 
  for(int i=0; i< sizeArray;){
    //If we found a number which is the divisible for numerator and denominator
    if((f.numerator()%aPrimes[i]==0)&&(f.denominator()%aPrimes[i]==0))
      f.set(f.numerator()/aPrimes[i],f.denominator()/aPrimes[i]);
    
    else
      ++i;
  }
  return f;
}

//End

Cascading Member Function

Have you ever see in a language programming similar expression like this one: a=b=c?

If you’re wondering why this is possible, it’s named method cascading, which implies return a reference of the pointer this (pointer pointing data of its own instance). Therefore, method cascading allows multiple functions to be called in the same expression (usually overloading operator functions) by returning same types that works as parameter of the next function. The way it works is from the right to the left of the expression: in the next code the operator += started in the instance f4 where frac is f3; we created a temp variable to hold the addition of f4 and f3, and subsequently return temp as parameter to the next operation with f5.

f3{1,16}; //Numerator,Denominator
f4{1,2};
f5{5,4}

//its equal that
//f4=f4+f3;
//and
//f5=f5+f4;
f5+=f4+=f3;

Fraction& Fraction::operator +=(Fraction& frac){
    Fraction temp {static_cast<Fraction&&>(*this+frac)};
    *this=temp;
    return *this;
}

Encapsulation concept

Encapsulation is one of the pillar of OPP C++. It remains as a good developer practice to hide data as much as possible for the user (or another instances). In other words, encapsulation restricts direct access through the implementation of some important components, while it provides a well defined public interface.

Encapsulation gives client uniform and clean access to data and offers flexibility to change your implementation decisions later. For example, the Fraction class encapsulates the members m_numerator and m_denominator avoiding freely corruption of these variables by external functions. By way of example:

class Fraction{

private:
  int m_nNumerator;
public:
  int numerator()const{return m_nNumerator;}
  void setNumerator(int i){m_nNumerator=i;}
}

class BadFraction{
public:
  int m_nNumerator;
}

int main(){
  
  Fraction fraction;

  //Only one secure way and easy to set  numerator
  fraction.setNumerator(2); 

  //Only one secure and easy wat to get numerator
  cout<<"Numerator: "<<fraction.numerator()<<endl;

  BadFraction badFraction;

  //Can be changed by everyone without control
  //The user mayn't understand the variable meaning...
  //Double highly error-prone
  badFraction.m_nNumerator=3;
  
  return 0;
}

Scott Meyers in “Effective C++” says that: “private is the only member access specifier which is capable to encapsulate, some developers think that protected also serves as well, but suppose that you delete some protected data from the base class, being that, the huge amount of code of the derived class might be broken, while if we delete private data members we’re sure that only affect a single class; that’s the real encapsulation”.

Constructor

Before talk about constructor, I wanna point out a interesting situation: When the compiler reach a class definition, it searches always for a constructordestructorcopy constructorand copy assignment operator; if they aren’t declared, the compiler create a default ones; due to, we have to have special attention to this functions in our class implementation, because they’re hiding practices that are decisive to create a sustainable software.

Constructor is a special member functions that controls the process of object initialization. A constructor must have the same name of the class, don’t return anything and be able to implement a member initialization list with it.

The primary advantage of using initialization lists is that usually allows better performance. For example check the next code:

//Constructor Template...

//ClassName::ClassName( parameterList )
//:initList
//{
//constructor body
//}

//Assignment based constructor
Fraction::Fraction(const int num, const int dem){ //Copy Constructor
  m_nNumerator=num; //Both, call copy assignment operator function
  m_nDenominator=dem;
}

//initialization list constructor
Fraction::Fraction(const int num, const int dem)
  :m_nNumerator(num),m_nDenominator(dem){
  if(m_nDenominator==0)
      m_nDenominator=1;

}

The first constructor implementation uses four calling operations: 2 default constructor and 2 copy assignment, this is not the case, but if the variables are large objects, this operation could be pretty expensive. In spite of that, the second constructor implementation class uses only a copy constructor for every declared member variable; 2 operations total . This means, that using initialization list constructor, we’re able to have 200% performance.

Copy constructor & Copy Assignment operator

Although constructor and destructor define the birth and dead of objects, respectively; the reproduction of objects belongs only for two operation: the copy constructor and the copy assignment operator. The copy constructor creates an identical copy of an existing object using the same class definition; likewise, the copy assignment operator overloads the symbol =, and creates an identical object from another one copying every non-static member variables.The next code shows how their syntax looks like:

class Fraction{
public:
    Fraction(const Fraction& fraction);
    Fraction& operator=(const Fraction& fraction);
};

//Copy Constructor
Fraction::Fraction(const Fraction &fraction){
    
}

//Copy Assignment operator
Fraction& Fraction::operator =(const Fraction& fraction){
    
}

Keyword explicit

The keyword explicit should be placed before constructors which have on single parameter definition (also called conversion constructors) to prevent compiler automatic conversions. This is useful when we don’t want implicit conversion that may infer in expensive operations.

For instance, the next code prevents an implicit creation of a Fraction, we’re defining two constructors: one with the explicit keyword and another without it, f1 calls the normal constructor with the risk that the compiler get confuse and calls a copy constructor(producing expensive operations and runtime errors), in contract with f2 that calls the no-conversion explicit constructor with an inexpensive double variable as parameter:

  //Conversion constructor

Fraction(int i);
explicit Fraction(double d);

//...
Fraction f1;
Fraction f2;
f1(1); //Can call a copy constructor instead.
f2(2.0); //Just pass the double easily.


There is another alternative to solve this problem: as we already know, the compiler creates copy constructor automatically if we hadn’t define one. To avoid the implicit conversion that we were talking about, you should define a copy constructor with type checking.

Destructor

The destructor is another special member function with the responsibility of cleanup a specific object. Its name is the same that the class name but preceded by a tilde (~). The destructor is called when: a local object goes out scope, a dynamic memory object is specifically destroyed by the operator delete,or just before the program terminates. A default compiled destructor calls every member variable destructor before destroys the main object.

Eventually, we’ll have a deep talk about these interesting features, but if you can’t wait, I recommend you read the book “Effective C++” from the item 5 to through item 13 to solve interesting questions. So that’s it!

References

Stroustrup, B. (2000). The C++ programming language. Boston: Addison-Wesley.

Leave a Reply

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