This is where Object-Oriented Programming (OOP) begins.
At the heart of OOP lies the concept of the class, which allows us to create user-defined data types and model real-world or logical entities directly in code.
From Data Types to Objects
An object is an instance of a class.
int,bool, andstringdescribe _what kind of data_ we store.
- A class describes:
- what data an object owns
- what operations it can perform
- how it is created
- how it is destroyed
A class can contain:
- variables (data members)
- pointers and references
- functions (member functions / methods)
Each object created from the class gets its own copy of the data but shares the same behavior definition.
Class
A class in C++ is declared using the class keyword, followed by the class name and a body enclosed in curly braces.
class MyClass {
public:
int myNumber; // Public attribute
void myFunction(); // Public method declaration
};
This declaration defines a type, not an object.
No memory is allocated until an object is created. (Just the blue print)
Access Specifiers: Controlling Visibility
Encapsulation is one of the core principles of OOP.
C++ enforces encapsulation using access specifiers:
- public
- Accessible from anywhere using the
.operator
- private (default)
- Accessible only inside the class
- protected
- Accessible inside the class and by derived classes
class Example {
private:
int secret;
protected:
int inheritedValue;
public:
int visible;
};
Why this matters
Access control prevents invalid states, enforces invariants, and enables safe APIs.
Member Functions (Methods)
Functions defined inside a class are called member functions.
They describe the behavior of the object.
class MyClass {
public:
int myNumber;
void myFunction() {
cout << "Hello World!" << endl;
}
};
Member functions automatically have access to the object’s internal state.
Creating and Using Objects
Once a class is defined, we can create objects (instances) of it.
MyClass myObj;
myObj.myNumber = 15;
myObj.myFunction();
At this moment:
- memory is allocated
- the constructor is executed
- the object becomes usable
Constructors: Object Construction
A constructor defines how an object is initialized.
Rules:
- Same name as the class
- No return type
- Called automatically when the object is created
class MyClass {
public:
int myNumber;
MyClass() {
myNumber = 10;
}
};
Writing `void` as a return type is **not allowed** in C++ constructors.
Member Initialization List
Initialization lists initialize members before the constructor body runs.
class MyClass {
public:
int myNumber;
MyClass(int num) : myNumber(num) {}
};
Why use them
- Required for
constmembers and references - More efficient
- Ensures correct initialization order
Separating Declaration and Definition (`::`)
Large systems separate interface from implementation.
class Rectangle {
int length;
int width;
public:
void setLength(int l);
int area();
};
void Rectangle::setLength(int l) {
length = l;
}
int Rectangle::area() {
return length * width;
}
The scope resolution operator :: binds the function definition to the class.
Types of Constructors
To use the different types of constructors
// Default constructor
Example exmaple;
// Parameterized constructor
Example example1(1,2);
// Copy constructor
Example example2 = example1;
// Moce constructor
Example example3 = std::move(example1);
// Constructor delegation
Example example4(1);
// Exceplicit constructor
// this will work with implicit constructor, and it will not work with explicit
Example example5 = 1;
// only this will work
Example example6(1,2);
Default Constructor
class Example {
public:
Example() {}
};
If not defined, the compiler may generate one—but members are not initialized unless explicitly done.
Parameterized Constructor
class Example {
public:
Example(int x, int y) {}
};
Used when object creation requires initial data.
Copy Constructor
Creates a new object from an existing one.
class Example {
public:
Example(const Example& obj) {}
};
Triggered when:
- Passing by value
- Returning by value
- Explicit copy
Move Constructor
Transfers ownership from a temporary object.
class Example {
public:
Example(int x) : Example(x, 0) {}
Example(int x, int y) {}
};
Why
- Avoids deep copies
- Enables high-performance code
Constructor Delegation
One constructor calls another.
class Example {
public:
Example(int x) : Example(x, 0) {}
Example(int x, int y) {}
};
Reduces duplication and enforces consistency.
Explicit Constructor
Prevents implicit conversions.
class Example {
public:
explicit Example(int x) {}
};
Example e1 = 1; // not allowed
Example e2(1); // allowed
The `this` Pointer
this points to the current object.
class Date {
int day, month, year;
public:
Date(int day, int month, int year) {
this->day = day;
this->month = month;
this->year = year;
}
};
Used to:
- resolve name conflicts
- return the object itself
- chain calls
Destructor: Object Destruction
The destructor is called when an object:
- goes out of scope
- is deleted
class Example {
public:
~Example() {
// cleanup
}
};
Every class that manages resources must define a destructor.
`default` and `delete`
`= default`
Normally the compiler will generate the constructor and the destructor automatically if it is not defined by the user, but you can control this behaviour by using default and delete keywords.
- The
defaultkeyword is used when you want the compiler to generate a default implementation of a constructor (or other special member functions like the copy constructor, move constructor, copy assignment operator, or move assignment operator).
#include <iostream>
class Example {
public:
int x;
// Default constructor using 'default'
Example() = default;
// Parameterized constructor
Example(int value) : x(value) {}
// Default copy constructor using 'default'
Example(const Example&) = default;
};
int main() {
// Calls the default constructor
Example e1;
// Calls the parameterized constructor
Example e2(10);
// Calls the default copy constructor
Example e3 = e2;
std::cout << "e1.x = " << e1.x << "\n"; // Undefined value (not initialized)
std::cout << "e2.x = " << e2.x << "\n"; // Output: e2.x = 10
std::cout << "e3.x = " << e3.x << "\n"; // Output: e3.x = 10
return 0;
}
Tells the compiler to generate the function.
`= delete`
The delete keyword is used when you want to prevent the compiler from generating a default implementation of a constructor (or other special member functions). It essentially disables the use of that function, preventing objects from being created or copied in a certain way.
#include <iostream>
class Example {
public:
int x;
// Default constructor
Example() : x(0) {}
// Parameterized constructor
Example(int value) : x(value) {}
// Delete the copy constructor
Example(const Example&) = delete;
// Delete the assignment operator
Example& operator=(const Example&) = delete;
};
int main() {
// Calls the default constructor
Example e1;
// Calls the parameterized constructor
Example e2(10);
// Error: Copy constructor is deleted
// Example e3 = e2;
// Error: Assignment operator is deleted
// e1 = e2;
std::cout << "e1.x = " << e1.x << "\n"; // Output: e1.x = 0
std::cout << "e2.x = " << e2.x << "\n"; // Output: e2.x = 10
return 0;
}
Prevents copying or assignment.
The behavior of the compiler varies based on what special members the user has defined. We can find details in the diagram by Howard Hinnant below:
| Default Constructor | Destructor | Copy Constructor | Copy Assignment | Move Constructor | Move Assignment | |
|---|---|---|---|---|---|---|
| User Declares Nothing | default | default | default | default | default | default |
| Any Constructor | Not declared | default | default | default | default | default |
| Default Constructor | User declared | default | default | default | default | default |
| Destructor | default | User declared | default | default | Not declared | Not declared |
| Copy Constructor | Not declared | default | User declared | default | Not declared | Not declared |
| Copy Assignment | default | default | default | User declared | Not declared | Not declared |
| Move Constructor | Not declared | default | deleted | deleted | User declared | Not declared |
| Move Assignment | default | default | deleted | deleted | Not declared |
Special Member Function Generation Rules
| Situation | Compiler Behavior |
|---|---|
| No user-declared members | All generated |
| Custom destructor | Move operations deleted |
| Custom copy constructor | Move operations deleted |
| Custom move constructor | Copy operations deleted |
(Howard Hinnant’s rules apply here exactly as in your table.)
Rule of 0 / 3 / 5 (Core of Modern C++)
Rule of 0 (Preferred)
If your class does **not** manage resources, do not write any special member functions.
class Person {
std::string name;
int age;
};
Let the compiler do everything.
Rule of 3 (Legacy / Manual Ownership)
If you manage a resource manually, you must define:
- Destructor
- Copy constructor
- Copy assignment operator
class Buffer {
int* data;
public:
~Buffer();
Buffer(const Buffer&);
Buffer& operator=(const Buffer&);
};
Rule of 5 (Modern C++)
Add move semantics:
- Move constructor
- Move assignment operator
class Buffer {
public:
~Buffer();
Buffer(const Buffer&);
Buffer& operator=(const Buffer&);
Buffer(Buffer&&);
Buffer& operator=(Buffer&&);
};
Why This Rule Exists
Because ownership must be unambiguous.
If you don’t explicitly define behavior, the compiler will—sometimes incorrectly for your intent.