C++ Constructors and Member Initialization
Learn C++ constructors, member initializer lists, declaration-order initialization, explicit conversions and validation before an inventory object becomes usable.
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.

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.
Construct one complete inventory value
The member initializer list establishes the owned string and integer before queries can be used.
Predict the output, edit the source, then select Run code.
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.
Trace members before the constructor body
Part logs initialization, not assignment. Member declarations and initializer entries use the same order.
Predict the output, edit the source, then select Run code.
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.
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.
Predict the output, edit the source, then select Run code.
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.
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.exeIndependent 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?
- The constructor body runs after member initialization, so validate the initialized member values before returning.
- Catch const std::invalid_argument& rather than swallowing every possible failure.
- A query returning a const string reference borrows from the item; do not retain it beyond that owner's lifetime.
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.
Predict the output, edit the source, then select Run code.
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
Reference lab solution
Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.
Predict the output, edit the source, then select Run code.
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.