Skip to content

Ch17: Classes

TL;DR: Encapsulation: Classes bundle variables and behaviors. Access specifiers (public, private) restrict visibility. Members default to private in classes and public in structs. | C++ compiles each .cpp file independently into an object file; headers (.h) share declarations between files, and the linker stitches everything together at the end — mixing this up causes either "undefined reference" or "multiple definition" errors.

⚡ Quick Reference

#include <iostream>
#include <string>

class MemberClass {
public:
    MemberClass() = default;
};

// Friend class forward declaration
class FriendClass;

class ClassName {
private:
    // Private default variables encapsulated
    int m_val {0};
    mutable int m_counter {0}; // mutable: bypasses const constraints

public:
    // ClassName() = default; - compiler-generated default constructor
    ClassName() = default;

    // ClassName() = delete; - deletes a constructor to block use
    ClassName(double) = delete;

    // Parameterized constructor using an initializer list
    ClassName(int val) : m_val(val) {}

    // Constructor delegation
    ClassName(int val, double) : ClassName(val) {}

    // Copy constructor representing deep copy
    ClassName(const ClassName& other) : m_val(other.m_val) {}

    // Destructor called automatically upon cleanup
    ~ClassName() {}

    // static - shared variable across all instances
    inline static int s_shared_val = 42; // inline static (C++17)

    // const method: promises not to modify class state
    int get_value() const {
        m_counter++; // allowed because of mutable
        return m_val;
    }

    // return *this; - returns self reference to enable method chaining
    ClassName& set_value(int val) {
        m_val = val;
        return *this;
    }

    // Friend function granted private access
    friend void print_private(const ClassName& obj);
};

// Friend function definition
void print_private(const ClassName& obj) {
    std::cout << obj.m_val << std::endl;
}

// struct - default access specifier is public
struct Point {
    int x;
    int y;
};

int main() {
    ClassName obj(10);
    obj.set_value(20).get_value(); // Dot notation & method chaining

    ClassName* p_obj = &obj;
    p_obj->get_value(); // Arrow notation

    const ClassName const_obj(5); // const object

    Point p{1, 2};
    auto [x, y] = p; // Structured bindings (C++20)

    return 0;
}

Build Model & Separate Compilation

// ---- math_utils.h (header: declarations only) ----
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int square(int x);          // declaration only, no body

#endif // include guard prevents double-inclusion

// ---- math_utils.cpp (translation unit: definition) ----
#include "math_utils.h"

int square(int x) {          // actual implementation lives here
    return x * x;
}

// ---- main.cpp (another translation unit) ----
#include "math_utils.h"
#include <iostream>

int main() {
    std::cout << square(5) << '\n'; // linker resolves this call to math_utils.cpp
    return 0;
}

// Build steps (conceptually):
// g++ -c math_utils.cpp -o math_utils.o   <- compile
// g++ -c main.cpp -o main.o               <- compile
// g++ math_utils.o main.o -o app          <- link

🧠 Core Concepts

  • Encapsulation: Classes bundle variables and behaviors. Access specifiers (public, private) restrict visibility. Members default to private in classes and public in structs.
  • Object Lifecycle: Constructors (parameterized or default) initialize member states, while destructors (~ClassName) perform cleanup and release heap allocations.
  • Const Correctness & Shared Members: Const objects call only const methods. Static members are shared across all class instances, representing class-level state.
  • Translation Unit: Each .cpp file, after the preprocessor expands its #includes, becomes one translation unit compiled independently into an object file (.o).
  • Declaration vs Definition: A declaration (in .h) tells the compiler a function/variable exists; a definition (in .cpp) provides the actual implementation. Headers should contain declarations so multiple .cpp files can share them without conflict.
  • The Linker: After all translation units are compiled separately, the linker combines the object files and resolves calls between them into a single executable — this is where "undefined reference" and "multiple definition" errors originate, not compile-time.
  • Include Guards: #ifndef/#define/#endif (or #pragma once) prevent a header from being processed twice within the same translation unit, which would otherwise cause duplicate declaration errors.

⚠️ Pitfalls (Quick Scan)

Mistake Fix
Forgetting the semicolon after a class definition Always end class declarations with a semicolon (;)
Accessing private class members directly from outside code Use public: specifier or getters/setters to expose members
Attempting parameterless instantiation after defining parameterized constructors Explicitly add ClassName() = default; to restore default instantiation
Making constructors private in normal classes Keep constructors public unless implementing specific patterns like Singleton
Using default memberwise copy constructor with raw pointer members Write custom copy constructors to allocate new memory and perform deep copies
Calling non-const methods on const objects Mark read-only member functions with the const suffix
Returning pointers or references to local variables from const methods Return values by value or return references to class member variables
Returning mutable references from getter methods Overload getters to return const references for const access
Allowing single-argument constructors to perform implicit conversions Declare single-argument constructors as explicit
Assigning values in constructor body instead of using initializer lists Always initialize members using constructor initializer list syntax
Using static linkage keyword (static) for internal functions Wrap helper functions inside anonymous namespaces to achieve internal linkage
Defining a non-inline symbol in multiple header files without inline modifiers Place declarations in headers and definitions in single source files, or use the inline keyword
Defining a function body directly in a header Only declare in .h; define in the matching .cpp (unless inline)
Missing include guard / #pragma once Add one to every header to prevent double-inclusion errors
Forgetting to link an object file Ensure every .cpp producing needed symbols is compiled and linked
Declaring a function but never defining it anywhere Compiles fine, fails at link with "undefined reference"
Defining the same non-inline function in two .cpp files Causes "multiple definition" at link time — keep definitions in exactly one .cpp

📖 Full Details

Cause → Effect → Fix with timestamp (click to expand) * **Forgetting the semicolon after a class definition** -> **Compile-time syntax errors** -> **Always end class declarations with a semicolon (`;`) (50:00)** * **Accessing private class members directly from outside code** -> **Compile-time error stating that members are inaccessible** -> **Use `public:` specifier or getters/setters to expose members (51:00)** * **Attempting parameterless instantiation after defining parameterized constructors** -> **Compile-time error because compiler stops generating default constructor** -> **Explicitly add `ClassName() = default;` to restore default instantiation (52:00)** * **Making constructors private in normal classes** -> **Compile-time error because client code cannot instantiate objects** -> **Keep constructors public unless implementing specific patterns like Singleton (53:00)** * **Using default memberwise copy constructor with raw pointer members** -> **Multiple objects share the same pointer address, causing double-free crashes on destruction** -> **Write custom copy constructors to allocate new memory and perform deep copies (54:00)** * **Calling non-const methods on const objects** -> **Compile-time error because const objects cannot invoke state-modifying actions** -> **Mark read-only member functions with the `const` suffix (56:00)** * **Returning pointers or references to local variables from const methods** -> **Undefined behavior because local variables are destroyed upon return** -> **Return values by value or return references to class member variables (57:00, 58:00)** * **Returning mutable references from getter methods** -> **Allows callers to bypass private encapsulation and modify internal state** -> **Overload getters to return const references for const access (59:00)** * **Allowing single-argument constructors to perform implicit conversions** -> **Silent creation of temporary objects from matching primitive types, causing logical errors** -> **Declare single-argument constructors as `explicit` (60:00)** * **Assigning values in constructor body instead of using initializer lists** -> **Members are default-initialized first then assigned, causing performance overhead and compile errors for const/ref members** -> **Always initialize members using constructor initializer list syntax (62:00)** * **Using static linkage keyword (`static`) for internal functions** -> **Works but `static` is deprecated for non-member internal functions in modern standards** -> **Wrap helper functions inside anonymous namespaces to achieve internal linkage (64:00)** * **Defining a non-inline symbol in multiple header files without inline modifiers** -> **Linker error stating multiple definitions of the same symbol** -> **Place declarations in headers and definitions in single source files, or use the `inline` keyword (65:00)** * **Defining a function body directly in a header without `inline`** -> **Each `.cpp` that includes the header gets its own copy, causing "multiple definition" linker errors** -> **Keep only declarations in headers; put the body in one `.cpp`, or mark it `inline` if it truly must live in the header** * **Missing include guard / `#pragma once`** -> **If the header gets included twice in one translation unit (common with nested includes), the compiler sees duplicate declarations** -> **Always wrap headers in `#ifndef/#define/#endif` or use `#pragma once`** * **Forgetting to link an object file that defines a needed symbol** -> **Compilation succeeds per-file, but the final link step fails with "undefined reference to ..."** -> **Make sure every `.o` file containing a called function is included in the final link command** * **Declaring a function in a header but never defining it in any `.cpp`** -> **Every file that includes the header compiles fine individually, but linking fails since no implementation exists** -> **Write the corresponding definition in exactly one `.cpp` file** * **Defining the same non-`inline` function identically in two `.cpp` files** -> **Linker finds two symbols with the same name and refuses to pick one — "multiple definition" error** -> **Ensure exactly one translation unit defines any given non-inline function or global variable**

📎 Repo Files

  • 26.Classes/26.2YourFirstClass/main.cpp
  • 26.Classes/26.3Constructors/main.cpp
  • 26.Classes/26.4DefaultedConstructors/main.cpp
  • 26.Classes/26.5SettersAndGetters/main.cpp
  • 26.Classes/26.6ClassAcrossMultipleFiles/
  • 26.Classes/26.8ManagingClassObjectsThroughPointers/main.cpp
  • 26.Classes/26.9Destructors/main.cpp
  • 26.Classes/26.10OrderOfConstructorDestructorCalls/main.cpp
  • 26.Classes/26.11ThisPointer/main.cpp
  • 26.Classes/26.12Struct/main.cpp
  • 26.Classes/26.13.SizeOfClassObjects/main.cpp
  • 27.ZoomingInOnClassObjects/27.2ConstObjects/
  • 27.ZoomingInOnClassObjects/27.3ConstObjectsAsFunctionParameters/
  • 27.ZoomingInOnClassObjects/27.4ConstMemberFunctions/
  • 27.ZoomingInOnClassObjects/27.5GettersThatDoubleAsSetters/
  • 27.ZoomingInOnClassObjects/27.6DanglingPointersAndReferences/
  • 27.ZoomingInOnClassObjects/27.8MutableObjects/
  • 27.ZoomingInOnClassObjects/27.9StructuredBindings/
  • 28.DivingDeepIntoConstructorsAndInitialization/28.2DefaultParametersForConstructors/
  • 28.DivingDeepIntoConstructorsAndInitialization/28.3InitializerListsForConstructors/
  • 28.DivingDeepIntoConstructorsAndInitialization/28.5ExplicitConstructors/
  • 28.DivingDeepIntoConstructorsAndInitialization/28.6ConstructorDelegation/
  • 28.DivingDeepIntoConstructorsAndInitialization/28.7CopyConstructors/
  • 28.DivingDeepIntoConstructorsAndInitialization/28.8ObjectsStoredInArraysAreCopies/
  • 28.DivingDeepIntoConstructorsAndInitialization/28.10MoveConstructors/
  • 28.DivingDeepIntoConstructorsAndInitialization/28.11DeletedConstructors/
  • 29.Friends/29.2FriendFunctions/
  • 29.Friends/29.3FriendClasses/
  • 30.ConstAndStaticMembers/30.3StaticMemberVariables/
  • 30.ConstAndStaticMembers/30.4InlineStaticMemberVariables/
  • 30.ConstAndStaticMembers/30.9StaticMemberFunctions/
  • 30.ConstAndStaticMembers/30.11InClassMemberVariableInitialization/
  • 31.Namespaces/31.2CreatingNamespaces/
  • 31.Namespaces/31.6UsingDeclarations/
  • 31.Namespaces/31.7AnonymousNamespaces/
  • 32.ProgramsWithMultipleFiles/32.2CompilingAndLinking_Model/
  • 32.ProgramsWithMultipleFiles/32.4DeclarationsAndDefinitions/
  • 32.ProgramsWithMultipleFiles/32.5OneDefinitionRule/
  • 32.ProgramsWithMultipleFiles/32.6Linkage/
  • 32.ProgramsWithMultipleFiles/32.9InlineVariablesAndFunctions/
  • 32.ProgramsWithMultipleFiles/main.cpp