Skip to main content
Ajay Dhangar
EditReport

Classes and Objects in C++

Object-Oriented Programming (OOP) is a programming paradigm built around the concept of "objects"—data structures that contain both data fields and modular functions.

C++ was originally engineered as "C with Classes" to bridge low-level system speeds with high-level structural modeling. At the heart of this paradigm lies the Class, which serves as user-defined blueprints for objects.

1. Defining a Class and Instantiating Objects

A Class is a logical template or blueprint. An Object is a physical instance of that class allocated in memory. Classes encapsulate data (attributes) and behaviors (methods) into a single cohesive unit.

Syntax

Class Definition Syntax
class ClassName {
public:
// Attributes (Data Members)
// Methods (Member Functions)
private:
// Encapsulated Members
}; // Note the required trailing semicolon

Implementation Example

Class and Object Example
#include <iostream>
#include <string>

class Vehicle {
public:
std::string brand;
int manufactureYear;

void displayTelemetry() {
std::cout << "Brand: " << brand << " | Year: " << manufactureYear << "\n";
}
};

int main() {
// Instantiating an object on the Stack
Vehicle fleetCar1;

// Accessing public data members using the dot (.) operator
fleetCar1.brand = "Toyota";
fleetCar1.manufactureYear = 2024;

fleetCar1.displayTelemetry(); // Output: Brand: Toyota | Year: 2024
return 0;
}

2. Access Modifiers & Data Encapsulation

C++ features three explicit access specification labels that enforce Data Encapsulation—the process of hiding raw internal state details from external manipulation.

  • public: Accessible by any statement inside the entire application scope.
  • private: Accessible only by member functions belonging internally to the class. This is the default modifier if none are specified.
  • protected: Accessible within the class itself and any child classes derived from it.

Enforcing Mutator and Accessor Methods (Getters/Setters)

Mutator and Accessor Methods
#include <iostream>
#include <string>

class Employee {
private:
int salary; // Restricted access

public:
std::string name;

// Setter (Mutator Method) with defensive data validation
void setSalary(int baselineSalary) {
if (baselineSalary >= 0) {
salary = baselineSalary;
}
}

// Getter (Accessor Method)
int getSalary() {
return salary;
}
};

int main() {
Employee engineer;
engineer.name = "Alice";
engineer.setSalary(95000);

std::cout << engineer.name << " Earns: $" << engineer.getSalary() << "\n";
return 0;
}

3. Constructors

A Constructor is a distinct initialization function called automatically by the compiler runtime environment the exact moment an object is instantiated.

  • Constructors match the class name precisely and do not possess a return type (not even void).
  • If you write a class without any constructor, the compiler silently provisions a hidden Default Constructor for you.
Constructor Example
#include <iostream>
#include <string>

class ServerNode {
public:
std::string nodeIp;
int portCode;

// Parameterized Constructor using modern Member Initializer List syntax
ServerNode(std::string ip, int port) : nodeIp(ip), portCode(port) {
std::cout << "Initialization complete for Node: " << nodeIp << "\n";
}
};

int main() {
// Passing values directly to constructor upon object creation
ServerNode edgeGateway("192.168.1.50", 8080);
return 0;
}

4. Destructors

A Destructor is invoked automatically when an object's lifetime expires (such as going out of scope or being explicitly targeted via delete).

  • Destructors cannot accept any arguments, cannot be overloaded, and are prefixed with a tilde character (~).
  • They serve as the backbone of RAII (Resource Acquisition Is Initialization) in C++, cleaning up open file streams, network sockets, or raw heap memory.
Destructor Example
#include <iostream>

class MemoryBuffer {
private:
int* dynamicArrayPointer;

public:
MemoryBuffer() {
dynamicArrayPointer = new int[100]; // Allocating Heap memory
std::cout << "Heap memory buffer allocated.\n";
}

~MemoryBuffer() {
delete[] dynamicArrayPointer; // Deallocating memory to prevent memory leaks
std::cout << "Heap memory successfully reclaimed by OS.\n";
}
};

5. Inheritance: Hierarchical Extensions

Inheritance permits an engineer to construct a child class (Derived Class) that takes on data properties and processing logic from an existing parent class (Base Class), promoting radical code reuse.

Inheritance Example
#include <iostream>

// Base Class
class HardwareComponent {
public:
void supplyPower() {
std::cout << "Power routing steady.\n";
}
};

// Derived Class inheriting publicly
class ComputeCore : public HardwareComponent {
public:
void executeInstruction() {
std::cout << "Cycle instruction pipeline processed.\n";
}
};

int main() {
ComputeCore primaryProcessor;
primaryProcessor.supplyPower(); // Inherited method capability
primaryProcessor.executeInstruction(); // Custom specialized capability
return 0;
}

6. Polymorphism: Multiform Adaptations

Polymorphism translates to "many forms". It allows interfaces to adapt dynamically depending on data context inputs or class layout variants.

A. Compile-Time Polymorphism (Function Overloading)

Different signatures under an identical method name. The compiler resolves which function block to trigger during compilation based on parameter traits.

Function Overloading Example
#include <iostream>

void logEvent(int errorCode) {
std::cout << "System Error Code: " << errorCode << "\n";
}

void logEvent(std::string statusMsg) {
std::cout << "System Event Message: " << statusMsg << "\n";
}
int main() {
logEvent(404); // Output: System Error Code: 404
logEvent("Connection established."); // Output: System Event Message: Connection established.

return 0;
}

B. Run-Time Polymorphism (Function Overriding)

Allows a derived class to replace or specialize an operation inherited from a base class. To enable runtime resolution via pointers or references, the base class signature must be marked with the virtual keyword.

Function Overriding Example
#include <iostream>

class BaseFirmware {
public:
// Virtual function enables Dynamic Binding runtime checks
virtual void bootSequence() {
std::cout << "Executing generic legacy boot steps...\n";
}

// Virtual destructor is critical for safe polymorphic deallocations
virtual ~BaseFirmware() = default;
};

class CustomOS : public BaseFirmware {
public:
// Explicit override declaration
void bootSequence() override {
std::cout << "Executing optimized kernel subsystem loading...\n";
}
};

int main() {
// Polymorphic pointer mapping base types to derived instances
BaseFirmware* systemKernel = new CustomOS();

systemKernel->bootSequence(); // Output: Executing optimized kernel subsystem loading...

delete systemKernel;
return 0;
}

7. Deep Dive: Functions Architecture in C++

While classes handle data structure compilation encapsulation, pure functions manage isolated compute pathways.

A. The Separation of Prototype Declarations vs Definitions

In production professional application templates, declarations generally exist within localized header files (.h), whereas execution code layouts live within companion implementation compilation files (.cpp).

Function Prototype vs Definition
#include <iostream>

// 1. Function Prototype Declaration (Contract signature given to the compiler)
int computeFactorial(int baselineInteger);

// 2. Main Entry Orchestration Block
int main() {
int executionResult = computeFactorial(5);
return 0;
}

// 3. Complete Function Definition
int computeFactorial(int baselineInteger) {
if (baselineInteger <= 1) return 1; // Base case termination
return baselineInteger * computeFactorial(baselineInteger - 1); // Recursive loop path
}

B. Specialized Structural Modifiers

1. Inline Performance Optimization (inline)

Prepending a function declaration with the inline compiler flag suggests copying the function's internal statements straight into the execution call site rather than spawning a structural context stack frame swap. This minimizes execution overhead for minor, frequently called methods.

Inline Function Example
inline int deriveSquare(int value) {
return value * value;
}

2. Default Input Method Arguments

You can assign default fallback parameter values. If an argument is omitted during execution calls, the pre-assigned fallback steps in automatically.

Default Arguments Example
#include <iostream>

// Default arguments must always be stacked toward the rightmost tail of the parameters
void configureNetworkNode(std::string nodeName, int portAssignment = 80) {
std::cout << "Node: " << nodeName << " bound to Port: " << portAssignment << "\n";
}

int main() {
configureNetworkNode("ProxyAlpha", 443); // Overrides default mapping
configureNetworkNode("ProxyBeta"); // Uses default fallback port 80
return 0;
}

8. Functional Paradigms Summary Matrix

Strategy SelectionExecution Timing ResolutionPerformance Overhead VectorsBest Applied Use Case Environments
Inline FunctionsCompile-Time Substitution00 stack frame context latency. Expanded binary size footprint.Short, localized, high-frequency utility steps.
Function OverloadingCompile-Time ResolutionIdentical to traditional function calls. No runtime cost.Processing varying numerical data input layouts under single concepts.
Virtual OverridingRun-Time Dynamic BindingSmall indirection penalty via virtual method lookup pointer tables (Vtables).Constructing flexible, open plugin architectures and modular object abstractions.

Conclusion

C++'s rich tapestry of class and function features empowers developers to architect complex, high-performance applications with precision control over memory management, execution flow, and code organization. Mastery of these concepts is essential for leveraging the full potential of C++ in modern software development.

Finished reading? Mark this topic as complete.