Classes and Structures

For larger programming projects, it can be very helpful to group code up into “classes,” which represent a set of functions and variables that are all related in some way. For example, when we start working on robotics programming, you’ll see that we use classes for representing robot components like motors, controllers, sensors, and the robot itself. A motor class, for instance, may contain functions for turning motors on and off. A controller class may contain functions for checking if certain buttons are pressed.

By putting code in classes, we can easily organize and separate out certain functionality so our code is easier to work with.

Here’s a program that contains a Motor class (which, at the moment, mostly just prints messages to the screen):

#include <iostream>
#include <string>

class Motor {
    private:
        std::string name;

    public:
        Motor(std::string name) {
            std::cout << "Beginning motor constructor\n";
            this->name = name;
        }
        void start(float speed) {
            std::cout << "Starting motor " << name << " with speed: " << speed << "\n";
        }
        void stop() {
            std::cout << "Stopping motor " << name << "\n";
        }
};

int main() {
    Motor frontLeftMotor("Motor0");
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();
    return 0;
}

Program output:

Beginning motor constructor
Starting motor Motor0 with speed: 0.8
Stopping motor Motor0

Now let’s walk through how this works:

  • A class is created by typing class followed by the name of the class (Motor in this case). Class names should always start with a capital letter.
  • Every variable and function in a class must be either private, public, or protected (which I’ll explain when going over “inheritance”). These three options are referred to as “access modifiers.”
    • In the Motor class, the name variable is private, and everything else is public.
    • A private variable or function cannot be accessed outside of a class.
      • It’s best to make as many variables and functions inside a class private as possible. This helps to separate (“encapsulate”) functionality from other code, which aids in organization.
    • A public variable or function can be accessed outside of a class.
  • Motor(int id) {...} is the “constructor” of the Motor class.
    • To use functions and variables in a class, we must first create an “instance” of that class (referred to as an “object”). This can be seen in line 22. There, we create a Motor object called “frontLeftMotor“.
    • When first creating a Motor object, the Motor constructor is called. In this case, the constructor has one parameter (called name). Therefore, when creating a Motor object in line 22, we put a name in parentheses ("Motor0" for instance).
  • Inside the Motor constructor, we set the motor’s private name variable equal to the name parameter of the constructor. This allows the other Motor functions to use the name variable.
    • When referring to the motor’s private name variable, we need to precede “name” with “this->“. “this” is just a pointer to the current class instance (frontLeftMotor in this case).
    • The arrow symbol (“->“) is how we access variables and functions from the pointer to an object. Therefore, “this->name” represents the name of frontLeftMotor.
    • The only reason “this->” is used here is because the constructor has a parameter called “name“. Without “this->“, the computer wouldn’t know which “name” you were referring to.
    • By naming the constructor’s parameter to something else, like “nameParam“, you could just type “name = nameParam;“.
  • start and stop are two other public functions inside the Motor class. They behave just like normal functions.
  • In the main function, we create a Motor instance called frontLeftMotor that’s given the name “Motor0”. Then, we call the start and stop functions on the frontLeftMotor.
    • The period is used to access variables and functions of an object.
      • Try it: See what happens when typing “frontLeftMotor.name“.
    • The start and stop functions cannot be called like normal functions. They can only be used after a “.” following a Motor object.

Phew! That was a lot. Now let’s go over some miscellaneous topics:

Ordering Functions and Variables in a Class

The order of functions and variables inside a class does not matter. For example, all of the programs below are equivalent to the first sample program:

#include <iostream>
#include <string>

class Motor {
    public:
        Motor(std::string name) {
            std::cout << "Beginning motor constructor\n";
            this->name = name;
        }
        void start(float speed) {
            std::cout << "Starting motor " << name << " with speed: " << speed << "\n";
        }
        void stop() {
            std::cout << "Stopping motor " << name << "\n";
        }

    private:
        std::string name;
};

int main() {
    Motor frontLeftMotor("Motor0");
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();
    return 0;
}
#include <iostream>
#include <string>

class Motor {
    public:
        Motor(std::string name) {
            std::cout << "Beginning motor constructor\n";
            this->name = name;
        }
        void start(float speed) {
            std::cout << "Starting motor " << name << " with speed: " << speed << "\n";
        }
        
    private:
        std::string name;

    public:
        void stop() {
            std::cout << "Stopping motor " << name << "\n";
        }
};

int main() {
    Motor frontLeftMotor("Motor0");
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();
    return 0;
}

Default Constructor

If you don’t specify a constructor for a class, that class is automatically given a default constructor, in which each variable inside the class is given a default value. In the case of the Motor class, the name variable is set to an empty string (“”):

#include <iostream>
#include <string>

class Motor {
    private:
        std::string name;

    public:
        void start(float speed) {
            std::cout << "Starting motor " << name << " with speed: " << speed << "\n";
        }
        void stop() {
            std::cout << "Stopping motor " << name << "\n";
        }
};

int main() {
    Motor frontLeftMotor;
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();
    return 0;
}

Program output:

Starting motor  with speed: 0.8
Stopping motor

We can also create our own default constructor by defining a constructor with no parameters:

#include <iostream>
#include <string>

class Motor {
    private:
        std::string name;

    public:
        Motor() {
            name = "DEFAULT NAME";
        }
        void start(float speed) {
            std::cout << "Starting motor " << name << " with speed: " << speed << "\n";
        }
        void stop() {
            std::cout << "Stopping motor " << name << "\n";
        }
};

int main() {
    Motor frontLeftMotor;
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();
    return 0;
}

Program output:

Starting motor DEFAULT NAME with speed: 0.8
Stopping motor DEFAULT NAME

Classes can contain multiple constructors, as long as each constructor has a different set of parameters:

#include <iostream>
#include <string>

class Motor {
    private:
        std::string name;

    public:
        Motor() {
            name = "DEFAULT NAME";
        }
        Motor(std::string name) {
            this->name = name;
        }
        void start(float speed) {
            std::cout << "Starting motor " << name << " with speed: " << speed << "\n";
        }
        void stop() {
            std::cout << "Stopping motor " << name << "\n";
        }
};

int main() {
    Motor frontLeftMotor;
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();

    Motor frontRightMotor("Motor1");
    frontRightMotor.start(0.6);
    frontRightMotor.stop();

    return 0;
}

Program output:

Starting motor DEFAULT NAME with speed: 0.8
Stopping motor DEFAULT NAME
Starting motor Motor1 with speed: 0.6
Stopping motor Motor1

Just remember not to to use parentheses when creating a class instance with a default constructor.

Destructor

A destructor is the opposite of a constructor; it’s automatically called when an object of the destructor’s class goes out of scope or is deleted (in the case of it being on the heap). Destructors look just like constructors, except they contain a tilde character (“~“) before the class name, and they cannot have any parameters. They also should generally be public.

#include <iostream>

class SampleClass {
    public:
        ~SampleClass() {
            std::cout << "SampleClass destructor called!\n";
        }
};

void sampleFunction() {
    std::cout << "sampleFunction beginning.\n";
    SampleClass sampleClass;
    std::cout << "sampleFunction end.\n";
}

int main() {
    sampleFunction();
    std::cout << "sampleFunction finished.\n";
    return 0;
}

Program output:

sampleFunction beginning.
sampleFunction end.
SampleClass destructor called!
sampleFunction finished.

Because the SampleClass object called “sampleClass” goes out of scope at the end of sampleFunction, its destructor is called then.

Destructors are often used to delete any heap variables before instances of their class go out of scope (thus preventing memory leaks). Doing this is referred to as RAII.

Static Functions and Variables

Functions and variables in classes can be made “static,” which makes them accessible without an instance of their class. To access static functions and variables, use two colons (“::”) following the class name. Here’s an example:

#include <iostream>
#include <string>

class Motor {
    private:
        std::string name;

    public:
        const static std::string MANUFACTURER;

        Motor(std::string name) {
            std::cout << "Beginning motor constructor\n";
            this->name = name;
        }
        void start(float speed) {
            std::cout << "Starting motor " << name << " with speed: " << speed << "\n";
        }
        void stop() {
            std::cout << "Stopping motor " << name << "\n";
        }
        static std::string className() {
            return "Motor";
        }
};
const std::string Motor::MANUFACTURER = "CTR Electronics";

int main() {
    std::cout << "All of the robot's motors are made by " << Motor::MANUFACTURER << "\n";
    std::cout << "Motor class name: " << Motor::className() << "\n";
    Motor frontLeftMotor("Motor0");
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();
    return 0;
}

Program output:

All of the robot's motors are made by CTR Electronics
Motor class name: Motor
Beginning motor constructor
Starting motor Motor0 with speed: 0.8
Stopping motor Motor0

Always be aware that, to set the value of a class’s static variable, you need to set its value outside of the class, and you also need to include the variable’s data type (including the word “const” if applicable and excluding the word “static“). I’ve got no idea why doing this is so complex…

Static functions and classes can be very useful for any aspects of a class that won’t be different among different instances. For example, different motor objects can be given different names, so the name variable inside the Motor class isn’t static. However, all of our robot’s motors are made by CTR Electronics, so the MANUFACTURER variable is static.

Initializer Lists

If a class contains const variables that you want to set within the class’s constructor, then you need to use an initializer list. Setting a const variable equal to some other value in the usual manner doesn’t work, which you can see here:

#include <iostream>
#include <string>

class Motor {
    private:
        const std::string NAME;

    public:
        Motor(std::string name) {
            std::cout << "Beginning motor constructor\n";
            NAME = name;
        }
        void start(float speed) {
            std::cout << "Starting motor " << NAME << " with speed: " << speed << "\n";
        }
        void stop() {
            std::cout << "Stopping motor " << NAME << "\n";
        }
};

int main() {
    Motor frontLeftMotor("Motor0");
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();
    return 0;
}

This code should not compile because you cannot set const variables equal to a new value after the const variable is created (since the variable must be constant).

In order to make this program work, we can use an initializer list:

#include <iostream>
#include <string>

class Motor {
    private:
        const std::string NAME;

    public:
        Motor(std::string name) : NAME(name) {
            std::cout << "Beginning motor constructor\n";
        }
        void start(float speed) {
            std::cout << "Starting motor " << NAME << " with speed: " << speed << "\n";
        }
        void stop() {
            std::cout << "Stopping motor " << NAME << "\n";
        }
};

int main() {
    Motor frontLeftMotor("Motor0");
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();
    return 0;
}

To use an initializer list, just put a colon after the constructor’s parentheses, followed by the name of one of the class’s variables with the value to set that variable equal to in parentheses. Multiple variables can go in an initializer list, separated by commas.

Structures

In classes, all variables and functions are private by default. This means that the code in the last sample program is equivalent to this:

#include <iostream>
#include <string>

class Motor {
    const std::string NAME;

    public:
        Motor(std::string name) : NAME(name) {
            std::cout << "Beginning motor constructor\n";
        }
        void start(float speed) {
            std::cout << "Starting motor " << NAME << " with speed: " << speed << "\n";
        }
        void stop() {
            std::cout << "Stopping motor " << NAME << "\n";
        }
};

int main() {
    Motor frontLeftMotor("Motor0");
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();
    return 0;
}

In C++, a “struct” (structure) is the same as a class, except its values are public by default. This means that a Motor structure could look like this:

#include <iostream>
#include <string>

struct Motor {
    Motor(std::string name) : NAME(name) {
        std::cout << "Beginning motor constructor\n";
    }
    void start(float speed) {
        std::cout << "Starting motor " << NAME << " with speed: " << speed << "\n";
    }
    void stop() {
        std::cout << "Stopping motor " << NAME << "\n";
    }

    private:
        const std::string NAME;
};

int main() {
    Motor frontLeftMotor("Motor0");
    frontLeftMotor.start(0.8);
    frontLeftMotor.stop();
    return 0;
}

Most of the time, it’s better to use a class than a structure because classes are better at “encapsulating” (separating) your code than structures by default (This just helps with organization. Things can get messy if all of your code can access everything from every class/structure).

Pointers to Classes and Structures

Whenever passing a class or structure instance to a function, it is good practice to pass that object by pointer or by reference. This is because classes and structures can take up a large amount of memory if they contain many variables and functions. By passing by pointer or by reference, we can be more efficient and avoid copying all of that data.

Just remember that an object pointer’s variables and functions are accessed with the arrow symbol (“->“) rather than a period. Here’s a program showing how an object pointer may be used:

#include <iostream>
#include <string>

class Motor {
    private:
        std::string name;

    public:
        Motor(std::string name) {
            this->name = name;
        }
        void start(float speed) {
            std::cout << "Starting motor " << name << " with speed: " << speed << "\n";
        }
        void stop() {
            std::cout << "Stopping motor " << name << "\n";
        }
};

void slowTurnRight(Motor *leftMotor, Motor *rightMotor) {
    leftMotor->start(0.5);
    rightMotor->start(0.2);
}

int main() {
    Motor frontLeftMotor("Motor0");
    Motor frontRightMotor("Motor1");

    slowTurnRight(&frontLeftMotor, &frontRightMotor);

    frontLeftMotor.stop();
    frontRightMotor.stop();

    return 0;
}

Program output:

Starting motor Motor0 with speed: 0.5
Starting motor Motor1 with speed: 0.2
Stopping motor Motor0
Stopping motor Motor1

The arrow symbol is actually just shorthand for combining the dereferencing operator (“*“) and a period. In the program above, leftMotor->start(0.5); is the same as (*leftMotor).start(0.5); . However, I’m sure you can see that typing “->” is a bit easier.

Object Oriented Programming

The use of classes in code is often called “object oriented programming” because instances of classes represent “objects” that can be used and manipulated. Object oriented programming is one of many programming “paradigms” (methodologies) that exist, and it is often very useful for large projects (like the code for our robot!) due to its ability to easily organize our code. We’ll be using classes a lot for our robot, so be sure you fully understand how they work before moving on!

Fun fact: C++ was created in order to turn the C programming language into a language that would support object oriented programming. C does have structures, but they’re very limited because they can only contain variables (not functions). C++ updated structures to allow for functions, added classes, and also added many more features that are really handy to have available (vectors, for instance).

You may notice now that many of the C++ features we’ve worked with so far are actually just classes: std::vector, std::string, and std::cout are all classes.