SovranCode
Modern C++ Lesson 23
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++ Classes and Objects: Encapsulation
NEXT LESSONC++ Const Member Functions and Composition
C++ foundations · Lesson 23 About 45 min with practice

C++ Constructors and Member Initialization

Learn C++ constructors, member initializer lists, declaration-order initialization, explicit conversions and validation before an inventory object becomes usable.

Before you begin

Understand class access sections and invariants. This lesson previews throwing and catching invalid_argument solely to report a construction that cannot establish valid state.

What you will learn

  • Initialize members rather than assigning them after initialization.
  • Keep initializer lists in member declaration order.
  • Reject construction that cannot establish a valid object.
  • Use explicit for an intentional single-argument construction boundary.
  • Test invalid identifiers and quantities without publishing a partial record.

A constructor establishes the object's starting promise

A default zero quantity was easy to make valid, but an inventory item may require both an identifier and a starting count. A constructor supplies those required values at creation. Its name matches the class and it has no return type. Once construction completes successfully, the caller should receive an object whose documented initial rules hold, rather than an object that still needs a separate initialize call before ordinary queries are safe.

The lab's InventoryItem accepts a title-like identifier of one through twenty bytes containing something other than ordinary spaces, and a count from zero through one hundred. This identifier policy is deliberately modest: it is not a uniqueness check, database lookup, Unicode character count or universal whitespace definition. A constructor can enforce these local rules but cannot prove facts for which it has no data, such as whether an identifier already exists in another process.

Do not invent a fake default identifier merely to make construction convenient. If a meaningful object requires input, make that requirement visible in its constructor. Our class intentionally has no zero-argument constructor. A compilation error for InventoryItem item; would expose an incomplete request rather than creating a placeholder object whose validity every later method must question. Construction is an opportunity to reduce the number of states the rest of the program must handle.

A conceptual assembly sequence initializes id_, count_ and a derived total_ in their declared order before a completed object becomes available. The illustration emphasizes that rewriting the member initializer list does not change the declaration-based order.
The teaching sequence is conceptual, not a memory-layout claim. Members initialize in declaration order; the constructor body runs afterwards.

Member initializer lists are not body assignments

The colon after a constructor's parameter list introduces member initializers, such as id_{id} and count_{count}. These describe how the members start their lifetimes. Assignment statements inside the body happen later, after member initialization has already occurred. For a string, constructing a default value and then assigning another value can do unnecessary work; for some member types, there may be no valid default construction to assign after at all.

Default member initializers are useful when a member has one sensible starting value across constructors. An explicit initializer for that member in the selected constructor takes precedence. Keep the intent visible instead of mixing contradictory defaults and assignments. The reference inventory example initializes both required members directly, then validates the resulting representation in the body before the constructor can successfully return to the caller.

The implementation takes the identifier string by value and uses std::move to initialize the owning string member from that local parameter. Include <utility> for std::move. This permits use of move-enabled string construction; it is not a promise that every call transfers a heap allocation or that std::move itself moves bytes. Copy and move semantics receive a dedicated lesson later. The current correctness argument depends on ownership and validation, not a particular allocation strategy.

C++ PRACTICE RUNNER

Construct one complete inventory value

The member initializer list establishes the owned string and integer before queries can be used.

#include <iostream>
#include <stdexcept>
#include <string>
#include <utility>
class InventoryItem {
    std::string id_;
    int count_;
public:
    InventoryItem(std::string id, int count) : id_{std::move(id)}, count_{count} {
        if (id_.empty() || id_.size() > 20 || id_.find_first_not_of(' ') == std::string::npos)
            throw std::invalid_argument{"id"};
        if (count_ < 0 || count_ > 100) throw std::invalid_argument{"count"};
    }
    const std::string& id() const { return id_; }
    int count() const { return count_; }
};
int main() {
    const InventoryItem item{"SKU-7", 8};
    std::cout << "Item: " << item.id() << "\nCount: " << item.count() << '\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
Item: SKU-7
Count: 8

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

Declaration order wins, even if another order looks attractive

Non-static data members initialize in the order they are declared in the class, not the textual order in the initializer list. Keep both orders aligned so the code communicates what the language actually does. A member that depends on an earlier member must not be declared before that dependency and then read an uninitialized value. Compiler reorder warnings are design feedback to investigate, not punctuation noise to suppress.

The order-tracing example uses a small Part type that prints its label when constructed. Its three members are declared id_, count_, total_ and their initializer list follows that order. The log is therefore a deterministic trace of initialization before Ready is printed from the outer constructor body. The names illustrate dependencies, but no real monetary total or memory layout is being inferred from the demonstration's visual assembly metaphor.

A repair exercise is to reorder the initializer list in your own copy and observe your compiler's warning while the declaration order remains unchanged. Restore the matching order afterwards. Do not read an uninitialized dependent integer to demonstrate the mistake; a warning plus a safe logging trace is enough. Good examples should explain unsafe designs without asking learners to execute undefined behavior as if its output were meaningful evidence.

C++ PRACTICE RUNNER

Trace members before the constructor body

Part logs initialization, not assignment. Member declarations and initializer entries use the same order.

#include <iostream>
struct Part {
    explicit Part(const char* name) { std::cout << name << '\n'; }
};
class Assembly {
    Part id_;
    Part count_;
    Part total_;
public:
    Assembly() : id_{"id"}, count_{"count"}, total_{"total"} {
        std::cout << "Ready\n";
    }
};
int main() { Assembly value; }

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
id
count
total
Ready

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

Reject invalid construction and make conversions deliberate

A constructor cannot return false as its result. This lesson uses std::invalid_argument to signal that accepted parameter types nevertheless contain an invalid domain value. Throwing prevents successful construction of that complete object. The caller catches the specific exception at the application boundary and prints a stable Invalid item label. We are previewing this narrow mechanism, not prescribing exceptions for every routine validation error in every application.

If a constructor body throws, the complete object's destructor is not called because that construction did not finish. Already constructed member objects still receive their normal cleanup during unwinding. Our string member therefore manages its own storage even when validation fails. Do not add manual delete operations or assume that a partially constructed object must be returned so the caller can clean it up. The lifetime lesson develops the distinction with visible scope traces.

For a constructor that accepts one argument, explicit can require callers to request construction rather than obtain it through some implicit conversions. The compile-error example rejects copy initialization from an int; direct braced construction with that int is still possible. explicit does not validate the int's range or automatically prevent every narrowing conversion elsewhere. Type conversion policy and the class invariant are separate responsibilities that must both be expressed deliberately.

C++ PRACTICE RUNNER

Choose explicit construction instead of an implicit conversion

Intentional compile failure. Change Quantity value = 5 to Quantity value{5}; this example teaches conversion syntax, not the bounded quantity interface from the previous lesson.

class Quantity {
public:
    explicit Quantity(int) {}
};
int main() {
    Quantity value = 5;
    (void)value;
}
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.

Test the creation boundary as a complete operation

The lab reads an identifier line and one whole integer count line. Missing lines or malformed integer syntax produce Invalid input with status one. Successfully parsed parameters then enter the constructor, which accepts or throws according to its own invariant. Only after success are Item and Count printed. A constructor rejection produces Invalid item with status one, without a partial accepted record or a substitute count clamped into range.

Test count zero and one hundred, negative count and one hundred one, an empty identifier, an ordinary-space-only identifier, and identifiers of twenty and twenty-one bytes. A valid final count line without a newline is accepted. Extra lines after the required two are outside this single-item protocol. Whitespace at the edges of a valid nonblank identifier is preserved because this contract does not claim to normalize names or silently rewrite a person's input.

Explain which code establishes each promise: whole-line parsing establishes a valid integer representation, member initializers establish representation members, constructor checks establish domain validity, and const queries expose observations. A single happy-path transcript cannot prove all four stages. Keep the rejected cases and their exit statuses in your evidence, then review whether the interface lets any later public operation break the invariant it established at construction.

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

Construct an inventory item only when its identifier and initial count satisfy the type's documented rules.

Acceptance checklist

  • Keep identifier and count private and initialize them with a member initializer list in declaration order.
  • Accept an identifier of 1–20 bytes containing a byte other than an ordinary space, and a count in 0..100.
  • Read identifier and one complete integer count line; bad or missing input prints Invalid input and returns 1.
  • Throw invalid_argument for invalid domain parameters and catch it at the boundary as Invalid item with status 1.
  • Print Item and Count only after construction succeeds, using const queries.
Need a hint?
  1. The constructor body runs after member initialization, so validate the initialized member values before returning.
  2. Catch const std::invalid_argument& rather than swallowing every possible failure.
  3. A query returning a const string reference borrows from the item; do not retain it beyond that owner's lifetime.
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>
#include <string>
#include <utility>
class InventoryItem {
    std::string id_;
    int count_;
public:
    InventoryItem(std::string id, int count) : id_{std::move(id)}, count_{count} {
        if (id_.empty() || id_.size() > 20 || id_.find_first_not_of(' ') == std::string::npos)
            throw std::invalid_argument{"id"};
        if (count_ < 0 || count_ > 100) throw std::invalid_argument{"count"};
    }
    const std::string& id() const { return id_; }
    int count() const { return count_; }
};
int main() {
    std::string id;
    int count{};
    if (!std::getline(std::cin, id) || !readInteger(count)) {
        std::cout << "Invalid input\n"; return 1;
    }
    try {
        const InventoryItem item{id, count};
        std::cout << "Item: " << item.id() << "\nCount: " << item.count() << '\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

Item: SKU-7
Count: 8

Required values initialize owned members directly and in their declared order; validation happens before construction can succeed.

A constructor failure never produces a usable InventoryItem. The string member cleans up its own resources during unwinding.

The application reports parsing failure separately from invalid domain parameters, and it does not relabel unrelated exceptions as successful validation.

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.

  • Initialize members rather than assigning them after initialization.
  • Keep initializer lists in member declaration order.
  • Reject construction that cannot establish a valid object.
  • Use explicit for an intentional single-argument construction boundary.
  • Test invalid identifiers and quantities without publishing a partial record.

References and further reading

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

  • C++ draft: initializing bases and members
  • Microsoft: C++ constructors
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.

01Which order determines non-static member initialization?
02Does assigning a string inside a constructor body initialize it for the first time?
03What does explicit on a single-argument constructor address?
04What happens when this constructor throws?
05Why distinguish Invalid input from Invalid item?
PREVIOUS LESSONC++ Classes and Objects: Encapsulation
NEXT LESSONC++ Const Member Functions and Composition
ON THIS PAGELearning objectivesA constructor establishes the object's starting promiseMember initializer lists are not body assignmentsDeclaration order wins, even if another order looks attractiveReject invalid construction and make conversions deliberateTest the creation boundary as a complete operationIndependent labReviewKnowledge checkReferences
Course overview