Object-Oriented C++

C++ access modifiers:

Just like Java, C++ supports three different kinds of access modifiers: public, protected, and private. A public access modifier means it can be accessed by anything. A protected access modifier means something can be accessed by its class and subclasses only. A private access modifier means that thing can only be accessed by the class it’s in. Access modifiers are useful for encapsulation, because you will be making classes that have their own instance variables and functions which probably shouldn’t be used in any old way. You can have public methods like getters and setters, but they will be manipulating private or protected instance variables, which someone can’t directly access without using said public getters/setters. Recall that a getter returns the value of a private or protected instance variable, and a setter sets the value of a private or protected instance variable, but can also perform input validation so that it can’t be set to an incorrect value. Not all instance variables need getters, as some should be hidden away from the caller and only used internally (encapsulation). Some people dislike private/protected access modifiers because of how repetitive it can be to write getter and setter methods for all of your classes.

Some languages like Python just ditch access modifiers and let everything be public. This is simpler, but has its own issues associated with it. But the Python design philosophy is centered around simplicity, so things like access modifiers or manual memory management are not a part of the language, though they are parts of C++, which can be more complicated and daunting but also more robust in some cases.

But without further ado, here are examples of access modifiers in C++:

#include <iostream>

#include <string>

using namespace std;

class AccessExample {

private:

int somePrivateThing;

bool yesOrNo;

protected:

double someProtectedThing;

string someString;

public:

char somePublicChar;

};

int main() {

AccessExample objectExample = AccessExample();

return 0;

}

Access modifiers are usually associated with classes. The above code doesn’t really do much of anything when you run it, but the point was to show how you can use access modifiers. You can use the name of the access modifier followed by a colon, and then on a new line, list instance variables and functions that correspond to that particular access modifier. You can list multiple things in each category.

The above access modifier example doesn’t contain a constructor or even any initialization for the instance variables, which is bad. But it’s only intended to illustrate how access modifiers work in C++.

Class members (methods and attributes) in C++ are private by default. By contrast, in a struct, everything is public by default. But you can always specify your own access modifiers, depending on what you’re doing. What I’m trying to get at here is that, even if you think your code doesn’t have access modifiers because you didn’t write any, it still does, but they’re just default and implicit.

C/C++ structs:

#include <iostream>

using namespace std;

struct PersonStruct {

int age;

string firstName;

string lastName;

};

int main() {

return 0;

}

C++ classes:

#include <iostream>

using namespace std;

class Person {

int age;

string firstName;

string lastName;

};

int main() {

return 0;

}

C++ constructors, getters, and setters:

#include <iostream>

using namespace std;

class Person {

protected:

//instance vars

int age;

string firstName;

string lastName;

public:

// default constructor

Person() {

age = 1;

firstName = “Default”;

lastName = “Default”;

}

//overloaded constructor

Person(int age, string firstName, string lastName){

this->age = age;

this->firstName = firstName;

this->lastName = lastName;

}

//getters and setters

int getAge(){

return age;

}

void setAge(int age){

if ((age < 100) && (age > 0)) {

this->age = age;

}

else {

cerr << “invalid age specified” << endl;

}

}

string getFirstName(){

return firstName;

}

void setFirstName(string firstName){

if ((firstName.length() < 3) || (firstName.length() > 10)) {

cerr << “invalid name length” << endl;

} else {

this->firstName = firstName;

}

}

string getLastName(){

return lastName;

}

void setLastName(string firstName){

if ((lastName.length() < 3) || (lastName.length() > 10)) {

cerr << “invalid name length” << endl;

} else {

this->lastName = lastName;

}

}

};

int main() {

Person people [3];

people[0] = Person(28, “Alice”, “Smith”);

people[1] = Person(25, “Bob”, “Robinson”);

people[2] = Person(44, “Catherine”, “Williams”);

for (int i = 0; i < 3; i++) {

cout << people[i].getFirstName() << ” “;

cout << people[i].getLastName() << “, “;

cout << “age ” << people[i].getAge() << “, “;

cout << “software developer” << endl;

}

return 0;

}

C++ destructors, new, delete, and more:

The following example combines a lot of new concepts, but it still builds on the previous code example with the Person class.

One way you can delete the people[] array with the following modified version of the previous program, which makes use of pointers (*), ->, cin.ignore(), new, and delete. To make it easy to see the changes, all the newly added stuff is in bold.

When your program finishes running, the destructors (designated with a tilde, like ~Person()) will be called automatically, getting rid of any objects that were created. However, that’s not the only time destructors are useful. You can also use a destructor to destroy an object even before the user quits the program, or until it stops running on its own. You can destroy objects even in the middle of a program. Depending on your class, you might want to do more than just cout a message when the constructor is invoked. Perhaps if your class uses objects from multiple other classes, you might want to delete those too, within the destructor. Destructors are basically the manual version of garbage collection. Java is a garbage-collected language, meaning it takes care of deleting unused objects for you so that you don’t have to care about memory leaks and things like that. But in C++, you’re responsible for memory management, with the use of destructors and the delete keyword. You can also use a tool called Valgrind to find memory leaks in C++ programs.

Just like constructors, if you don’t provide one in the class, the program will make an implicit one. But they’re not very useful as they don’t do much.

#include <iostream>

using namespace std;

class Person {

protected:

//instance vars

int age;

string firstName;

string lastName;

public:

// default constructor

Person() {

age = 0;

firstName = “Default”;

lastName = “Default”;

}

//overloaded constructor

Person(int age, string firstName, string lastName){

this->age = age;

this->firstName = firstName;

this->lastName = lastName;

}

~Person() {

cout << “The destructor has been invoked for “;

cout << firstName << ” ” << lastName << endl;

}

//getters and setters

int getAge(){

return age;

}

void setAge(int age){

if ((age < 100) && (age > 0)) {

this->age = age;

}

else {

cerr << “invalid age specified” << endl;

}

}

string getFirstName(){

return firstName;

}

void setFirstName(string firstName){

if ((firstName.length() < 3) || (firstName.length() > 10)) {

cerr << “invalid name length” << endl;

} else {

this->firstName = firstName;

}

}

string getLastName(){

return lastName;

}

void setLastName(string firstName){

if ((lastName.length() < 3) || (lastName.length() > 10)) {

cerr << “invalid name length” << endl;

} else {

this->lastName = lastName;

}

}

};

int main() {

Person*people[3];

people[0] = new Person(28, “Alice”, “Smith”);

people[1] = new Person(25, “Bob”, “Robinson”);

people[2] = new Person(44, “Catherine”, “Williams”);

for (int i = 0; i < 3; i++) {

cout << people[i]->getFirstName() << ” “;

cout << people[i]->getLastName() << “, “;

cout << “age ” << people[i]->getAge() << “, “;

cout << “software developer” << endl;

}

cout << “Press enter to continue…” << endl;

cin.ignore();

delete people[0];

cout << “Press enter to continue…” << endl;

cin.ignore();

delete people[1];

cout << “Press enter to continue…” << endl;

cin.ignore();

delete people[2];

return 0;

}

Here is the output of the above program:

$ ./hello.exe

Alice Smith, age 28, software developer

Bob Robinson, age 25, software developer

Catherine Williams, age 44, software developer

Press enter to continue…

The destructor has been invoked for Alice Smith

Press enter to continue…

The destructor has been invoked for Bob Robinson

Press enter to continue…

The destructor has been invoked for Catherine Williams

And if you don’t use the lines that say delete for person[0], person[1], and person[2], here’s what the output will be:

$ $ ./hello.exe

Bob Robinson, age 25, software developer

Catherine Williams, age 44, software developer

Press enter to continue…

Press enter to continue…

Press enter to continue…

For every new, you need a delete. Otherwise, you get memory leaks. So the above output is bad. Notice how the destructor didn’t get called.

But if you don’t want to use pointers, here is an alternative, and an example of how destructors can be called automatically when a program finishes. In the below example, everything is the same except for the main function:

int main() {

Person people[3] = {Person(28, “Alice”, “Smith”), Person(25, “Bob”, “Robinson”), Person(44, “Catherine”, “Williams”)};

for (int i = 0; i < 3; i++) {

cout << people[i].getFirstName() << ” “;

cout << people[i].getLastName() << “, “;

cout << “age ” << people[i].getAge() << “, “;

cout << “software developer” << endl;

}

return 0;

}

When you aren’t using pointers, the way to access an object’s methods is through a period, like people[i].getFirstName(). But with pointers, you need to use -> instead. So the above example, which doesn’t use pointers, requires periods for method invocation. The way to tell the difference between the pointer and non-pointer versions is that the pointer one uses * and the non-pointer one doesn’t. The pointer one also had manual news and deletes, whereas the non-pointer one is automatic.

You’ll notice that all of the objects in the Person class array get destroyed and have their destructors called automatically. But in the pointer example, you need to pair every new with a delete.

new means to allocate memory for something. delete means to deallocate it. A pointer is something that stores the memory address of something else. this is a qualifier. . is a member operator, and -> is the pointer member operator. You can populate an array inside {}.

cin, which is used for getting user input, has a method called ignore(), which gets user input and does nothing with it. It can be useful if you want the program to pause execution and wait for the user to hit enter to continue, and it’s usually best to notify the user of this setup with a cout.

Abstract classes:

An abstract class is a class that cannot be instantiated. Instead, you need to create a concrete subclass which inherits from it.

Here is an abstract class in C++, which is made abstract not by a keyword for the class itself, but instead by the fact that it contains at least one pure virtual function:

class AbstractClass {

protected:

int whatever;

string something;

public:

//constructors can’t be virtual

//but typical functions like

//getters and setters can be

virtual int getWhatever() = 0;

virtual string getSomething() = 0;

virtual void setWhatever(int whatever) = 0;

virtual void setSomething(string something) = 0;

};

And here is a concrete class derived from the above abstract class:

class ConcreteClass: AbstractClass {

private:

double some_other_instance_var;

public:

int getWhatever(){

return whatever;

}

string getSomething(){

return something;

}

void setWhatever(int whatever){

this->whatever = whatever;

}

void setSomething(string something) {

this->something = something;

}

};

Interfaces:

C++ does not have support for interfaces. However, some people online say you can attempt to recreate interface functionality through the use of abstract classes. However, when learning previous programming languages, I was taught about the importance of the distinction between the two: abstract classes are used for inheritance and for an is-a relationship, such as a human being a more specific type of mammal. But interfaces are for a has-a relationship, such as a person owning a phone. But you’ll just have to accept that not every language has every feature. For example, Python doesn’t have switch/case or constructor overloading.

new keyword:

Similar to Java, new is used in C++ when you want to make something new that will take up memory.

int* myArray = new int[100];

new is typically used with pointers, indicated with an asterisk.

ConcreteClass* myClass = new ConcreteClass;

When using new, you will also need to use -> instead of . to access instance variable or class functions.

myClass->setSomething(“ok”);

C++ inheritance:

A subclass can inherit what a superclass has in C++. However, member accessibility will depend on access modifiers and what kind of inheritance is used (public, protected, or private).

In the following code example, there’s a base class called BaseClass, which is extended by a class called SubClass. SubClass inherits variables and functions from BaseClass. So a SubClass object can use things that were defined in the BaseClass class, just as long as they’re either public or protected, but not private. In the below example, SubClass can’t directly use the instance variables whatever or something, and instead has to use the public getter and setter methods:

#include <iostream>

using namespace std;

class BaseClass {

public:

void sayHi() {

cout << “hi” << endl;

}

int getWhatever(){

return whatever;

}

string getSomething(){

return something;

}

void setWhatever(int whatever){

this->whatever = whatever;

}

void setSomething(string something) {

this->something = something;

}

private:

int whatever;

string something;

};

class SubClass: public BaseClass {

private:

double some_other_instance_var;

public:

SubClass(int whatever, string something) {

setWhatever(whatever);

setSomething(something);

}

SubClass() {

setWhatever(0);

setSomething(“default”);

}

};

int main() {

SubClass myClass(123, “some string”);

myClass.sayHi();

myClass.setSomething(“hello”);

myClass.setWhatever(456);

cout << myClass.getSomething() << myClass.getWhatever() << endl;

return 0;

}

C++ headers, implementations, #ifndef, #define, and #endif:

Instead of just jumping right in and writing a book from beginning to end, word by word, you might want to come up with a basic structure first – some basic ideas for topics to write about. This structure might be called a skeleton. Coding is similar, because you don’t just write code from start to finish. You can start with notes, UML diagrams, or in C++, function headers in header files. A class header can make it easy for you to lay out the basic guidelines for what you want your code to do, all before you have to spend time implementing all of it. Your general ideas will be in a .h file for a class, such as MyCoolClass.h. Then, in a .cpp file, like MyCoolClass.cpp, you will code the implementation of the class that you made the header for.

It’s true that it’s a little more confusing at first to break it into two files, starting with the basics in a .h and then writing all the fine details in a .cpp, but once you get used to it, it can be convenient for writing your ideas down quickly in a way that isn’t just comments. There is some boilerplate involved, such as #ifndef, #define, and #endif, but it’s really not that bad.

Here is a class header example, called Person.h:

#ifndef CHAPTER6_DEMOS_PERSON_H

#define CHAPTER6_DEMOS_PERSON_H

#include <iostream>

using namespace std;

class Person {

private:

string firstName;

string lastName;

public:

Person(string firstName, string lastName);

Person(string first);

Person();

string getFullName();

string getFirstName();

string getLastName();

};

#endif //CHAPTER6_DEMOS_PERSON_H

#ifndef, #define, and #endif are preprocessor directives. They exist to prevent a developer from having the same code included twice. When you #include something in C++, it’s like copying and pasting it into that location. So to #include a class is to put it in the program. If, for some reason, something gets included twice, that can cause some problems. So #ifndef CLASSNAME_H means proceed only if CLASSNAME_H is not yet defined. Then it will define CLASSNAME_H with #define. Then, finally, after you’ve put all your code into the definition, you end it with #endif. Common convention is to name your header definitions like CLASSNAME_H or PROJECTNAME_CLASSNAME_H. Some IDEs might even automatically generate this boilerplate code for you.

You will notice that the functions aren’t defined. The functions in the header are just function headers. A full function consists of a function header and a function definition, but a class header file only contains function headers/declarations. You can declare a function without defining it right then and there. A function header consists of a return type, name, and arguments. You can technically just list the data types of the arguments rather than providing names in addition to types, but I went ahead and put both in.

As a header file is basically unfinished stuff, you will have to create a corresponding .cpp class file. Here is the Person.cpp file that goes along with the above Person.h file:

#include <iostream>

#include “Person.h”

using namespace std;

Person::Person(string firstName, string lastName) {

this->firstName = firstName;

this->lastName = lastName;

}

Person::Person(string first) {

firstName = first;

lastName = “”;

}

Person::Person() {

firstName = “Default”;

lastName = “Name”;

}

string Person::getFullName(){

return firstName + ” ” + lastName;

}

string Person::getFirstName() {

return firstName;

}

string Person::getLastName() {

return lastName;

}

And here is the main.cpp that drives the Person class for this example:

#include <iostream>

#include “Person.h”

using namespace std;

int main() {

Person alan(“Alan”);

cout << alan.getFirstName() << endl;

return 0;

}

Please note that, if you are compiling via the command line (such as with g++) as opposed to clicking the “run” or “build” button in an IDE, you will need to compile all the .cpp files, such as the following example:

$ g++ main.cpp Person.cpp -o multiple_test.exe

Notice that you don’t need to compile .h files.

C++ templates:

C++ templates are basically just generics. A template class can be used with many different data types. The vector example earlier in this chapter is an example of a template.

Here is an (admittedly overly-simplified) example of a stack data structure that is templatized so it can be used with any type. A stack data structure is like a stack of plates: you can add stuff to the top of it, but the only thing you can take off of it is the topmost item. The functions in a stack are push (to add an item to the stack), pop (to get something off the stack), and peak (to view the topmost item without taking it off). In the following code example, I am focusing less on stack concepts and more on templates, so I am using an intentionally limited example of a stack, so as to not detract too much from the explanation of templates:

#include <iostream>

using namespace std;

template <class T>

class Stack {

private:

T stackArray[1000];

//not resizable but oh well

int top; //topmost item in stack

public:

Stack() {

top = -1;

}

T pop() {

if (top == -1) {

cerr << “error: nothing to pop” << endl;

exit(1);

} else {

T popped = stackArray[top];

top -= 1;

return popped;

}

}

void push(T item) {

if (top == 999) {

cerr << “error: no room in stack” << endl;

} else {

top += 1;

stackArray[top] = item;

}

}

T peak() {

if (top == -1) {

cerr << “error: nothing to peak” << endl;

exit(1);

} else {

return stackArray[top];

}

}

};

int main() {

cout << “stack/template demo” << endl;

Stack<int> myStack;

myStack.push(123);

myStack.push(456);

myStack.push(789);

cout << myStack.pop() << endl;

cout << myStack.peak() << endl;

return 0;

}

Notice how, instead of having a stack with a specific type, it is of type T, which is the template type, which is specified when a template object is created. In this case, Stack<int> means that the Stack template class will be used with int as its type, so all instances of T will be replaced with int in the Stack template class. You could also easily make a Stack class with string, double, float, or a user-created class such as Person, like in the previous code examples in this section.

If you are ever looking at code and see angle brackets <> or the letter T, odds are you’re dealing with templates (or they might be called generics, depending on which language you’re using).

← Previous | Next →

C++ Topic List

Main Topic List

Leave a Reply

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