Understanding Object-Oriented Programming in C++ (Part 1)

Lokesh Bihani
7 min readApr 18, 2024

--

Welcome to the first part of my 6-part series on object-oriented programming in C++! In this series, I aim to break down key OOP concepts to make them accessible and easy to understand.

While there are many excellent resources available for learning OOP in Java, the same cannot be said for C++. While “The C++ Programming Language” by Bjarne Stroustrup is an exceptional resource, from which I’ve drawn extensively for this series, its length and complexity may deter some learners.

My goal with this series is to provide a concise yet comprehensive overview of OOP concepts in C++, allowing readers to grasp these concepts quickly and without the need for extensive study.

In this first part, I’ll lay the foundation by explaining fundamental concepts before diving into more advanced topics. So, without further ado, let’s start!

There are many programming paradigms offered by a programming language and ‘Object oriented programming (OOP)’ is one of them. It revolves around the concepts of objects and classes.

It’s worth noting that not all programming languages support classes and objects. For example, languages like Erlang, Haskell, and F# adhere to different paradigms such as functional programming. In these languages, emphasis is placed on functions as first-class citizens and immutable data structures, leading to a different programming style compared to OOP.

Classes & Objects

In simple terms, a class serves as a blueprint or template for creating objects, while an object is the actual instance built using that blueprint.

For instance, in a vehicle manufacturing plant, before producing a specific car model like the Honda Accord, a detailed design or blueprint is first created. This blueprint outlines various properties of the car, such as its color, dashboard features, and seating configuration, as well as behaviors like starting music from the steering wheel or activating indicators.

Properties and Behaviors

Properties represent the characteristics or attributes of an object, while behaviors define its actions or functionalities.

For example, in the context of the Honda Accord, properties would include its color, seating material, and number of seats, while behaviors could encompass functions like starting the engine or activating safety features.

Lvalue and Rvalue references

Lvalue References

An lvalue reference is a reference to an object that already exists and has a name. Think of an lvalue reference as a “label” or “alias” for an existing object.

Lvalue references are created using the & symbol in variable declarations. Example: int x = 10; int& ref = x;

Rvalue References

An rvalue reference is a reference to a temporary or expiring object that doesn’t have a name. Think of an rvalue reference as a “handle” to a temporary object that will soon be destroyed.

Rvalue references are created using the && symbol in variable declarations. Example: int&& tempRef = 10;

In short, lvalue references are used to refer to existing objects, while rvalue references are used to refer to temporary objects that are about to be discarded.

Const and Constexpr

Constants in C++ is a vast topic, but understanding the basics is essential for comprehending this article. There are two primary ways to create constant variables: constand constexpr.

The fundamental difference lies in when their values are determined. A constexpr variable must be initialized at compile time, ensuring its value is computed during the compilation process. In contrast, a const variable can be initialized either at compile time or run time. Once initialized, both const and constexpr variables are immutable, meaning their values cannot be altered thereafter.

const int runtimeConstant = someFunction(); // Initialized at run time
constexpr int compileTimeConstant = 42; // Initialized at compile time

One notable advantage of constexpr variables is their ability to enable compile-time evaluation of expressions, which can lead to optimization opportunities and potentially enhance performance in specific scenarios.

C++ class definition syntax

class Vehicle {

}; // This semicolon is important. Don't forget it.

Access Modifiers

  • Private: Members accessible only from within a class. Everything defined in C++ by default is private.
  • Public: Members accessible from anywhere.
  • Protected: Members accessible from the sub classes and the class they’re defined in.

Static and non-static data members

The term ‘Data members’ means any member of the class that stores data.

Instance members (non-static data members)

Each object of the class has its own set of instance members and they’re accessed using the dot (.) operator on the object.

class Car {
public:
string brand; // Instance member
int year; // Instance member
};

int main() {
Car myCar; // Creating an instance of the Car class
myCar.brand = "Ford";
myCar.year = 2022;

Car yourCar; // Creating another instance of the Car class
yourCar.brand = "Toyota";
yourCar.year = 2019;

return 0;
}

Static data members or static variables

They are shared among all instances of the class and are accessed using the scope resolution operator (::) with the class name.

Important: If a class member is non-const, then it is always defined and initialized outside the class. In our example totalAccounts is non-const. If we try to do static int totalAccounts = 0; , the compiler will throw this error:

cpp:6:16: error: ISO C++ forbids in-class initialization of non-const static member 'BankAccount::totalAccounts'
6 | static int totalAccounts = 0; // Class member
| ^~~~~~~~~~~~~

If you still feel the need to initialize it inside the class, use static const int totalAccounts = 0;

class BankAccount {
public:
static int totalAccounts; // Class member
BankAccount() {
totalAccounts++; // Accessing and modifying the class member
}
};

// Definition and initialization of the class member
int BankAccount::totalAccounts = 0;

int main() {
BankAccount acc1; // totalAccounts is incremented
BankAccount acc2; // totalAccounts is incremented
BankAccount acc3; // totalAccounts is incremented

cout << "Total accounts: " << BankAccount::totalAccounts << endl; // Accessing class member
return 0;
}

Generally, static members are declared inside the class and defined outside the class. However, for a few simple special cases, it is possible to initialize a static member in the class declaration:

  • A constexpr of a literal type, and the initializer must be a constant-expression. or,
  • The static member must be a const of an integral or enumeration type.
class Curious {
public:
static const int c1 = 7; // OK [Rule 2]
static int c2 = 11; // error : not const
const int c3 = 13; // OK, but not static. It's an instance member variable
static const int c4 = sqrt(9); // OK [Rule 2]
static const float c5 = 7.0; // error : in-class not integral
static contexpr float c6 = 7.0; // OK [Rule 1]
};

If (and only if) you use an initialized member in a way that requires it to be stored as an object in memory, the member must be (uniquely) defined somewhere. This is what it essentially means:

class Curious {
public:
static const int c1 = 10;
};

// Unique definition. If we skip this, compiler will throw error
const int Curious::c1;

int main() {
const int* p = &Curious::c1; // OK: Curious::c1 has been defined
}

Static and non-static member functions

A member function is a function that is defined as part of a class. They’re sometimes also referred as methods.

Instance functions (Non-static member Functions)

They are declared inside the class and are invoked using objects of the class. Every instance method is associated with the objects of the class.

Instance methods also have access to a special pointer named this , which points to the calling object.

class Circle {
private:
double radius;

public:
void setRadius(double r) { // instance function
radius = r;
}

double getArea() { // instance function
return 3.14 * radius * this->radius;
}
};

int main() {
Circle myCircle;
myCircle.setRadius(5.0);
double area = myCircle.getArea(); // Calling instance function using the object
cout << "Area of the circle: " << area << endl;

return 0;
}

Static member functions

Static member functions on the other hand, are not bound to any object; they do not have a this pointer and cannot access instance members directly.

Typically, they’re called directly using the class name. However, they can also be called using the object of that class. It’s essential to note that, calling static methods using object of the class doesn’t grant them special access to non-static data members. In other words, regardless of whether you call non-static data members using the class name or the object, they’re never accessible to static methods.

Static member functions may not be declared as const. (I’ll discuss about this more later while discussing about const member functions.)

class Account {
public:
// Member functions can use static members directly, without the scope operator.
void calculate() {
amount += amount * interestRate;
}

// static member function declaration
static double rate();

static void rate(double);

private:
string owner;
double amount;
static double interestRate;
static double initRate();
};

// When we define static member function outside the class, we don't use static keyword again.
double Account::rate() {
return interestRate;
}

int main() {
Account ac1;
Account *ac2 = &ac1;

// equivalent ways to call the static member rate function
double r = ac1.rate(); // through an Account object or reference
r = ac2->rate(); // through a pointer to an Account object

r = Account::rate(); // through scope resolution operator
return 0;
}

This is important:

A static data member can have the same type as the class type of which it is a member, whereas A nonstatic data member is restricted to being declared as a pointer or a reference to an object of its class.

class Bar {
public:
// . . .
private:
static Bar mem1; // ok: static member can have incomplete type
Bar *mem2; // ok: pointer member can have incomplete type
Bar mem3; // error: data members must have complete type
};

--

--

Lokesh Bihani

Software Engineer passionate about System Design, DevOps, and ML. I try to simplify complex tech concepts to help others learn while deepening my own knowledge.