Chapter 09

Classes and Objects

Encapsulation, access specifiers, constructors, destructors, copy/move semantics, object initialization, static members, const member functions, friends, unions, and the this pointer.

In this chapter

  1. Encapsulation & Abstraction
  2. Access Specifiers
  3. Constructors
  4. Default Constructors
  5. Initialization Lists
  6. Object Initialization Rules
  7. Constructor Delegation
  8. Destructors
  9. Copy Constructor
  10. Move Constructor
  11. The Rule of Three / Five
  12. Special Cases
  13. Static Members
  14. Static Objects
  15. Implicit Conversions & explicit
  16. Const Member Functions & mutable
  17. friend
  18. union
  19. Aggregate Initialization
  20. constexpr Constructor
  21. Nested Classes
  22. The this Pointer
1

Encapsulation & Abstraction

Encapsulation is the ability to logically group data and functions together. It is a core pillar of object-oriented programming — it keeps related state and behavior in one unit and controls external access to internals.

Abstraction empowers classes to decide which attributes should be visible outside. It hides implementation details and exposes only what the consumer needs to know. Another pillar of OOP.

class BankAccount {
private:
    double balance;          // hidden — nobody touches this directly
public:
    void deposit(double amt); // exposed — the only way in
    double getBalance();
};

2

Access Specifiers

SpecifierWho can access
privateOnly the class itself (and friends)
protectedThe class itself and derived (child) classes
publicAnyone
Default access differs by type:
  • class — members are private by default
  • struct — members are public by default
  • union — members are public by default
The only practical difference between class and struct in C++ is this default access level.

3

Constructors

A constructor is a special method invoked automatically when an object is instantiated. It has the same name as the class and no return type. Constructors can be overloaded.

class Student {
public:
    Student();              // default constructor
    Student(std::string n, int age);  // overloaded
};

Defining Outside the Class

You can define a constructor (or any member function) outside the class body using the scope resolution operator :::

Student::Student() {
    // definition
}

Dynamic Allocation

Student* s = new Student();
s->age = 6;  // -> operator dereferences the pointer and accesses the member
delete s;
Dynamic allocation (new) is not needed for simple value types. Prefer stack allocation unless you need the object to outlive the current scope or its size is dynamic.

4

Default Constructors

A default constructor is one that can be invoked without any arguments.

// Explicitly request the compiler-generated default (C++11)
Student() = default;

// Explicitly delete it to prevent default construction (C++11)
Student() = delete;

5

Initialization Lists

An initialization list follows the constructor signature with a colon : and initializes members before the constructor body runs. This is the preferred way to initialize members.

Student(std::string newName, int newAge)
    : name(newName), age(newAge)   // initialization list
{
    // body runs after members are already initialized
}
Always prefer initialization lists over assignment in the constructor body. Assignment in the body means the member is first default-constructed, then overwritten — two operations instead of one.

6

Object Initialization Rules

Initialization (preferred)
MyClass(int x, int y) : _x(x), _y(y) {}
// members are initialized directly
Default init + assignment (avoid)
MyClass(int x, int y) {
    _x = x;  // default-init then assign
    _y = y;  // two operations instead of one
}

Postpone Variable Definitions

Define variables as close to their first use as possible — and initialize them with their needed value immediately, skipping the default-construct-then-assign pattern:

// Not ideal — default constructed, then assigned
Point pt;
pt.x = 5;
pt.y = 5;

// Better — constructed directly with the right values
Point pt2(5, 5);

Loop Variable Tradeoff

Where you declare a loop variable affects how many constructor/destructor calls occur:

// Option A: 1 ctor + 1 dtor + n assignments
Object obj;
for (...) { obj.x = 6; }

// Option B: n ctors + n dtors
for (...) { Object x(6); }
Option A is generally more efficient — but only if assignment is cheaper than a constructor + destructor pair. For simple types this is almost always true; for complex types, measure.

7

Constructor Delegation (C++11)

A constructor can delegate to another constructor in the same class via the initialization list, avoiding code duplication:

Student() : Student(" ", 10) {}   // delegates to the 2-arg constructor
A delegating constructor cannot also have an initialization list — it either delegates or initializes directly, not both.

8

Destructors

A destructor is automatically invoked when an object is destroyed. It has the same name as the class prefixed with a tilde ~, takes no arguments, and returns nothing.

~Student();  // no return, no arguments

Objects are destroyed when:

  1. They go out of scope (stack objects)
  2. They are explicitly deleted with delete (heap objects)
If your class manages heap memory (raw new), you must define a destructor that calls delete — otherwise every object destruction is a memory leak.

9

Copy Constructor

The copy constructor is called when an object is initialized from another object of the same type. The compiler generates one automatically — but it only does a shallow copy.

Shallow Copy (default behaviour)

A shallow copy copies the pointer value — both the original and the copy end up pointing to the same heap memory. Destroying either one leaves the other with a dangling pointer.

Deep Copy (user-defined)

For classes that own heap memory, define your own copy constructor to allocate new memory and copy the data:

// Must be const ref — or the compiler won't accept it
Student(const Student& source);
//       ↑ same name   ↑ pass by ref (to avoid infinite recursion)
Copy constructors are invoked when passing/returning objects by value, but not when using the assignment operator (= on an existing object). You need to overload operator= separately to handle that case.

10

Move Constructor (C++11)

When a function returns an object by value, the copy constructor would normally be called to transfer the data — but for temporary objects this is wasteful. A move constructor lets you "steal" the resource instead of copying it.

Student(Student&& moveSource);
//      ↑ rvalue reference (&&) — binds to temporaries
Student myFunc();
Student newStudent(myFunc());   // move constructor invoked, not copy
The compiler will automatically use the move constructor over the copy constructor for temporaries and return values wherever possible (see Chapter 10 on RVO). Implementing a move constructor for resource-owning classes is part of the Rule of Five.

+

The Rule of Three / Five

If your class needs to define any one of these, it almost certainly needs to define all of them — because the need for one implies you're managing a resource that all of them must handle correctly.

Rule of Three (pre-C++11)Rule of Five (C++11+)
DestructorDestructor
Copy constructorCopy constructor
Copy assignment operatorCopy assignment operator
Move constructor
Move assignment operator
The cleanest approach for modern C++: use = default and = delete to be explicit, and prefer smart pointers so you don't need to manage memory at all — reducing to the Rule of Zero.

12

Special Cases

Non-Copyable Classes

To prevent an object from being copied, declare the copy constructor and copy assignment operator as private (pre-C++11) or = delete (C++11, preferred):

class Unique {
    Unique(const Unique&) = delete;
    Unique& operator=(const Unique&) = delete;
};
No need to implement functions you delete — declaring them deleted is sufficient.

Singleton Classes

A singleton permits exactly one instance of a class:

  1. Make the constructor, copy constructor, and assignment operator private
  2. Provide a public static method that returns the single instance
  3. Store the instance as a static variable inside that method
class Config {
public:
    static Config& GetInstance() {
        static Config instance;  // created once, lives forever
        return instance;
    }
private:
    Config() {}
    Config(const Config&) = delete;
};

Heap-Only Classes

To prevent stack instantiation, make the destructor private. Only heap instances are then allowed by the compiler. Provide a public static destroy function to allow deletion:

static void Destroy(Student* instance) {
    delete instance;  // valid — we're inside the class, so private dtor is accessible
}

13

Static Members

Static members belong to the class, not to any individual instance. They are shared across all objects of that type.

class Student {
public:
    static int count;
};

// Must be defined and initialized outside the class
int Student::count = 0;
Static data members must be initialized outside the class definition (at file scope). They are not initialized by any constructor.

14

Static Objects

A static object exists from the time it is constructed until the end of the program. There are two kinds:

KindWhere declaredInitialization
Local staticInside a functionOn the first call to that function
Non-local staticFile scope, namespace, classBefore main() — order across translation units is undefined
The relative initialization order of non-local static objects in different translation units is undefined. If object A depends on object B at startup and they're in different .cpp files, you may get a crash.

The fix: wrap non-local statics inside functions (making them local statics). They are guaranteed to be initialized the first time the function is called:

MyClass& getInstance() {
    static MyClass instance;  // initialized on first call, safe
    return instance;
}
This is essentially the Singleton pattern, and it's also how the "Meyers Singleton" works.

15

Implicit Conversions & explicit

A constructor that takes a single argument acts as an implicit conversion constructor — the compiler will use it to automatically convert from that argument type to the class type.

class Student {
public:
    Student(int age);  // single-argument constructor
};

void CheckStudent(Student s);

CheckStudent(10);     // ALLOWED — 10 is implicitly converted to Student(10)
Student s = 30;     // ALLOWED — implicit conversion constructor

Preventing Implicit Conversions with explicit

Mark the constructor explicit to require that conversion to be written out:

explicit Student(int age);

CheckStudent(10);          // ERROR — implicit conversion blocked
CheckStudent(Student(10));  // OK — explicit construction
explicit can also be applied to conversion operators. It's generally good practice to mark single-argument constructors explicit unless implicit conversion is specifically intended.

16

Const Member Functions & mutable

Const Member Functions

Member functions can be overloaded on const alone — a trailing const after the parameter list signals that the function will not modify any member variables:

{
public:
    void myFunc();
    void myFunc() const; // overloaded const version
}
Overloading on const can lead to code duplication. One workaround: have the non-const version call the const version, though this may require a const_cast. Rarely recommended.

Bitwise vs. Logical const

const member functions are bitwise const — the bits of the object are unchanged — but not necessarily logically const (they might modify data through a pointer member).

Use the mutable keyword to allow a specific member to change inside a const function — useful for caches or lazy-computed values:

{
public:
    void myFunc() const;
private:
    int var1;
    mutable int var2; // can be edited even inside myFunc() const
}

17

friend

The friend keyword grants an external function or class access to a class's private and protected members.

class Student {
private:
    int grade;
    friend void CalcArea();   // CalcArea can access Student's privates
    friend class Teacher;    // the entire Teacher class can access privates
};
friend is listed as one of C++'s "unsafe features" (Chapter 1). Use it sparingly — it breaks encapsulation. Prefer redesigning the interface before reaching for friend.

18

union

A union is a special class type where all non-static data members share the same memory location. Only one member is "active" at any given time — writing to one member invalidates all others.

union Variant {
    int   i;
    float f;
    char  c;
};  // sizeof(Variant) == sizeof(float) == 4

Variant v;
v.i = 42;    // i is active
v.f = 3.14;  // f is now active — reading v.i is undefined behavior
Modern C++ prefers std::variant over raw unions — it is type-safe and tracks which member is currently active.

19

Aggregate Initialization

Aggregate initialization lets you initialize an object's members with a brace-list, like an array:

Student newStudent = {"Peter", 28};  // name = "Peter", age = 28

A class is an aggregate only if it has all of:

In practice this only works for simple plain-data structs. Any real class with private state or constructors will not qualify.

20

constexpr Constructor

A constexpr constructor tells the compiler to evaluate the creation and instances of the class at compile time wherever possible.

constexpr Student(int age);

21

Nested Classes

A class defined inside another class. The inner class has access to all members of the enclosing class, but does not have access to the enclosing class's this pointer (unless given a reference to an instance).

class Student {
    int x;
public:
    class Inner {
        void foo() { x = 5; }  // can access Student::x
    };
};

Defining the inner class's methods outside requires two scope operators:

void Student::Inner::foo() { /* ... */ }
Good for hiding helper classes or structs that are implementation details of the outer class and shouldn't be visible in the outer namespace.

22

The this Pointer

Within the body of any non-static member function, an implicit this pointer is passed as a hidden parameter. It points to the object that is calling the member function.

class Student {
    std::string name;
public:
    Student& setName(std::string n) {
        this->name = n;  // disambiguates member from parameter
        return *this;   // enables method chaining
    }
};
Static member functions do not receive a this pointer — they belong to the class, not to any instance, so there's no "current object" to point to.
← Chapter 8 ↑ Index Chapter 10 →