SovranCode
Modern C++ Lesson 24
Course contents50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
Modern C++50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
PREVIOUS LESSONC++ Constructors and Member Initialization
NEXT LESSONC++ Copying Objects and the Rule of Zero
C++ foundations · Lesson 24 About 43 min with practice

C++ Const Member Functions and Composition

Learn C++ const member functions and composition with owning line items, const-correct total queries, bounded integer cents and a two-line order lab.

Before you begin

Complete classes, constructors, validated integers and functions. Understand that a const query describes access through an object rather than absolute global immutability.

What you will learn

  • Distinguish a const object, const member query and immutable member declaration.
  • Compose an Order from owned LineItem values without inheritance.
  • Make read-only queries callable across a const object graph.
  • Bound multiplication and addition with explicit price and quantity contracts.
  • Test zero-value and maximum-value composed reports.

A trailing const belongs to the member function's object access

A total query should be callable when an order is only available through a const reference. Write total() const to express that its implementation does not modify ordinary non-mutable members through the current object. The trailing qualifier is different from returning a const value, and different from making a member variable const. Reading those declarations as interchangeable obscures which operation the compiler is actually allowed to call.

A const object can call appropriately qualified const members, but not an ordinary non-const member merely because its current body happens to perform only arithmetic. The signature is the caller-visible promise. Put the qualifier on observing functions when that is their intended contract. Do not use const_cast to get around a missing qualifier in a teaching example; repair the interface and let the compiler check the intended read-only access path.

Const correctness is not a universal purity guarantee. A const method could access globals, write through a pointer member to a separate object, or modify a mutable cache. Our small totals do none of those things: they read owned numeric fields and return a computed integer value. Keep that concrete behavior separate from broader claims such as thread safety, determinism of arbitrary methods, or transitive immutability of everything an object might reference.

An Order boundary encloses two distinct owned LineItem values, each containing quantity and unit-price components. A separate total() const observation window reads the values without changing or extracting them, illustrating whole-part composition rather than inheritance.
Composition expresses an Order has LineItems. The const query path must remain read-only through each owned part.

Composition models a has-a relationship with ordinary values

An order has line items; it is not a specialized kind of line item. Represent that relationship with members rather than inheritance. In this deliberately small model, Order owns exactly two LineItem objects called first_ and second_. Their lifetimes are tied to the enclosing order's lifetime. No separate heap allocation, shared pointer or external database is required to represent this whole-part relationship in a single-file example.

A variable-size order could later store a vector of line-item values, but changing the collection shape is not necessary to learn composition. A fixed two-part model makes construction and query dependencies visible while reusing familiar values. State the limitation honestly: Lines: 2 is a property of this laboratory's representation, not a live commerce statistic or a claim that the code already supports a production shopping cart.

Each LineItem constructor protects its own unit-price and quantity bounds. The Order constructor accepts complete line-item values and initializes its members from them. This means the aggregate total can rely on those existing local guarantees rather than re-parsing original text. Composition becomes useful when each part offers an interface the larger type can use without learning how its representation was validated or how future internal changes are implemented.

C++ PRACTICE RUNNER

Query a const order through const line-item methods

All arithmetic uses already validated values. The returned total is in integer cents, not formatted currency or a verified payment.

#include <iostream>
#include <stdexcept>
class LineItem {
    int unitCents_;
    int quantity_;
public:
    LineItem(int unitCents, int quantity) : unitCents_{unitCents}, quantity_{quantity} {
        if (unitCents_ < 0 || unitCents_ > 10000 || quantity_ < 0 || quantity_ > 100)
            throw std::invalid_argument{"line item"};
    }
    long long total() const { return 1LL * unitCents_ * quantity_; }
};
class Order {
    LineItem first_;
    LineItem second_;
public:
    Order(LineItem first, LineItem second) : first_{first}, second_{second} {}
    long long total() const { return first_.total() + second_.total(); }
};
int main() {
    const Order basket{LineItem{850, 3}, LineItem{50, 1}};
    std::cout << "Total cents: " << basket.total() << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
Total cents: 2600

After editing inputs, calculate a new prediction; this reference describes only the starter.

Read-only access must work all the way through the query

Inside Order::total() const, ordinary owned members are observed through a const access path. Calling LineItem::total() therefore requires that part's query to be const as well. Qualifying only the outer query is insufficient when its implementation calls a non-const member on an owned part. The compiler error identifies a break in the observation contract, not evidence that composition itself requires making all objects mutable.

The intentional failure uses a minimal Counter with a read-only-looking count() body but no trailing qualifier. Calling it through a const Counter fails. Repair the signature to count() const and rerun, rather than removing const from the caller to hide the mismatch. Then compare with a legitimate mutation such as increment(): that operation should remain non-const because its purpose is to change ordinary state.

Do not confuse const member functions with const data members. Making a field const changes what assignments and generated special operations a class can support. It is not necessary just to offer a read-only view to a caller. Our numeric members remain ordinary private integers; access sections and the absence of mutation methods determine the available interface, while const query qualifiers allow sensible use from const objects and references.

C++ PRACTICE RUNNER

Repair a query signature rather than weakening its caller

Intentional compile error. Add a trailing const to count(), keeping the const object intact.

class Counter {
    int count_{3};
public:
    int count() { return count_; }
};
int main() {
    const Counter value;
    return value.count() == 3 ? 0 : 1;
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Define arithmetic bounds and return observations by value

The lab allows each unit price to range from zero through ten thousand cents and each quantity from zero through one hundred. One line total is therefore at most one million cents, and the two-line order total at most two million. Use long long for the result and start multiplication with 1LL so the expression uses that type from the beginning. The explicit contract is still important; a wider integer is not a proof against arbitrary unbounded inputs.

Integer cents avoid introducing floating-point display decisions into this lesson, but they do not constitute a complete money type. There is no currency conversion, tax calculation, decimal parser, refund policy or payment integration here. If a program later accepts a larger number of lines or quantities, re-evaluate the sum's range rather than assuming this two-line proof applies unchanged. Arithmetic guarantees depend on both data bounds and collection shape.

Return numeric totals by value and avoid exposing mutable references to the owned parts merely for convenience. A const reference getter, if provided, would still borrow from an owner whose lifetime matters; our total query does not need one. The smaller interface lets a caller ask the question it needs without taking responsibility for internal member lifetimes or bypassing the line-item constructor's rules through an unintended mutation path.

Validate the input boundary, then test the composed value

Read four complete integer lines in order: first unit price, first quantity, second unit price and second quantity. Missing or malformed required lines print Invalid input and return status one before construction. After successful parsing, build two line items and the const order inside a narrow try block. A domain-bound failure prints Invalid item and returns one; a successful report prints Lines: 2 followed by Total cents and the computed result.

Test zero price, zero quantity, both lines at their maximum, negative parameters and one-past-maximum parameters in every position. Include a malformed final token and a valid final line without a newline. A zero-quantity line remains an owned line item under this representation; it contributes zero but does not disappear from the declared count of two. Additional input after the four required lines is outside this one-report protocol and remains unread.

For a repair exercise, remove the trailing const from the inner total query and explain why the outer const order can no longer call it. Restore the qualifier, then verify that repeated total queries yield the same value without changing the parts. The purpose is to see composition as a chain of compatible contracts. It is not to memorize a rule that every method should be const or to treat a compiler error as a reason to discard useful access restrictions.

LOCAL TERMINAL COMMANDS

Build and investigate locally

Complete single-file programs use the compiler-backed editor when its sandbox is available. Otherwise save one program as main.cpp and build with your installed C++20 compiler. Expected output is a comparison target, not a substitute for execution.

macOS / Linux (choose your compiler):
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or: g++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
./lesson

Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exe
APPLY THE CONCEPT

Independent lab

Build a const two-line order whose observing methods compose without exposing writable internal parts.

Acceptance checklist

  • Read four whole integer lines: unit cents and quantity for each of two line items.
  • Accept unit cents in 0..10000 and quantity in 0..100, enforced by LineItem construction.
  • Distinguish malformed input as Invalid input from domain failure as Invalid item, both with status 1.
  • Own both LineItem values in Order and make each total query const.
  • Print Lines: 2 and Total cents using bounded integer arithmetic; zero quantities contribute zero.
Need a hint?
  1. The inner query must be callable when the outer const method observes its member.
  2. Use 1LL before multiplication rather than converting an already evaluated int product later.
  3. Validate by constructing parts before reporting; no partial success output should precede a rejected part.
C++ PRACTICE RUNNER

Write and run your lab

Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.

#include <iostream>
#include <string>
#include <sstream>
#include <limits>

int main() {
    // Implement the acceptance checklist above.
    return 0;
}

TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Your lab evidence and notes

Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.

Loading local notes…

Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.

Reveal the explained lab solution
C++ PRACTICE RUNNER

Reference lab solution

Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.

#include <iostream>
#include <sstream>
#include <string>
bool readInteger(int& value) {
    std::string line;
    if (!std::getline(std::cin, line)) return false;
    std::istringstream parser{line};
    if (!(parser >> value)) return false;
    parser >> std::ws;
    return parser.eof();
}
#include <stdexcept>
class LineItem {
    int unitCents_;
    int quantity_;
public:
    LineItem(int unitCents, int quantity) : unitCents_{unitCents}, quantity_{quantity} {
        if (unitCents_ < 0 || unitCents_ > 10000 || quantity_ < 0 || quantity_ > 100)
            throw std::invalid_argument{"line item"};
    }
    long long total() const { return 1LL * unitCents_ * quantity_; }
};
class Order {
    LineItem first_;
    LineItem second_;
public:
    Order(LineItem first, LineItem second) : first_{first}, second_{second} {}
    long long total() const { return first_.total() + second_.total(); }
};
int main() {
    int price1{}, quantity1{}, price2{}, quantity2{};
    if (!readInteger(price1) || !readInteger(quantity1) || !readInteger(price2) || !readInteger(quantity2)) {
        std::cout << "Invalid input\n"; return 1;
    }
    try {
        const Order basket{LineItem{price1, quantity1}, LineItem{price2, quantity2}};
        std::cout << "Lines: 2\nTotal cents: " << basket.total() << '\n';
    } catch (const std::invalid_argument&) {
        std::cout << "Invalid item\n"; return 1;
    }
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Expected output

Lines: 2
Total cents: 2600

The numeric parser and constructors establish different boundaries; neither phase prints an accepted order before all parts exist.

Order owns complete values and observes them through const-qualified methods, expressing composition without hierarchy or shared ownership.

The allowed ranges bound both multiplication and the two-part sum; integer cents are a teaching unit, not a payment or full financial model.

Review before moving on

Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.

  • Distinguish a const object, const member query and immutable member declaration.
  • Compose an Order from owned LineItem values without inheritance.
  • Make read-only queries callable across a const object graph.
  • Bound multiplication and addition with explicit price and quantity contracts.
  • Test zero-value and maximum-value composed reports.

References and further reading

Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.

  • Microsoft: const member functions
  • C++ Core Guidelines: class design
KNOWLEDGE CHECK

Check your understanding

Answer all five questions, then review the explanations. Answers are not saved or synced; completion is a separate self-review choice.

01What does total() const permit?
02What relationship does composition express here?
03Why must LineItem::total also be const?
04What does 1LL contribute to the total expression?
05Does a const method guarantee no side effect anywhere?
PREVIOUS LESSONC++ Constructors and Member Initialization
NEXT LESSONC++ Copying Objects and the Rule of Zero
ON THIS PAGELearning objectivesA trailing const belongs to the member function's object accessComposition models a has-a relationship with ordinary valuesRead-only access must work all the way through the queryDefine arithmetic bounds and return observations by valueValidate the input boundary, then test the composed valueIndependent labReviewKnowledge checkReferences
Course overview