SovranCode
Modern C++ Lesson 25
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++ Const Member Functions and Composition
NEXT LESSONC++ Object Lifetime, Destructors and RAII
C++ foundations · Lesson 25 About 44 min with practice

C++ Copying Objects and the Rule of Zero

Understand C++ value semantics and the Rule of Zero with independent string/vector copies, copy initialization versus assignment, lifetime boundaries and a record-copy lab.

Before you begin

Complete structs, classes, construction, strings and vectors. This lesson uses ordinary owning members; raw pointer ownership and move-state guarantees are separate later topics.

What you will learn

  • Explain independent copies for a record owning string and vector members.
  • Distinguish copy construction from copy assignment.
  • Recognize aliasing through a reference as different from copying a value.
  • Apply the Rule of Zero without confusing resource safety with domain validity.
  • Test empty and nonempty copied collections without invalid access.

A value copy represents another independently editable record

A record containing a std::string title and a std::vector<int> can usually use the compiler-generated copy operations. Copying that record copies each member according to the member's own semantics. A copied string owns its text value and a copied vector owns its integer sequence. Editing those members in the copy does not edit the original record's corresponding members. That useful behavior comes from the chosen member types, not merely the word struct.

This is value semantics in a concrete example: the new object initially represents the same data but has its own lifetime and can change independently. It does not require all values to occupy identical addresses or produce a particular number of allocations. Small-string optimization and allocation strategies are implementation details. Test observable title and element values rather than printing addresses and treating an accidental layout as the definition of correctness.

Contrast auto copy = original with auto& alias = original. The first creates another object from the original value; the second creates a reference to the existing object. A write through alias changes that existing object because no independent record was created. Both forms are useful when intentional. The name copy does not create independence if its declaration actually uses a reference, so read the type and initializer rather than trusting the variable's label.

Two separate record trays represent original and copy. Each owns its own title tile and sequence of integer tiles. A one-way copy arrow denotes initial duplication, while later highlighted edits appear only in the copy and no shared storage connects the two records.
Independence follows from these owning string and vector members. A record containing pointers or shared owners would need a different analysis.
C++ PRACTICE RUNNER

Change a copied record without changing its source

The copied vector receives another integer and the copied string receives another title. No writable alias is returned or stored.

#include <iostream>
#include <string>
#include <vector>
struct Record {
    std::string title;
    std::vector<int> readings;
};
int main() {
    const Record original{"Morning", {2, 4}};
    auto copy = original;
    copy.title = "Revised";
    copy.readings.push_back(8);
    std::cout << original.title << ": " << original.readings.size() << '\n';
    std::cout << copy.title << ": " << copy.readings.size() << '\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
Morning: 2
Revised: 3

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

Copy construction and assignment happen at different boundaries

Record copy = original creates a new record and initializes its members from the source. It is copy initialization of a new object, not assignment to an already existing record despite the equals sign. By contrast, copy = another after copy already exists invokes assignment. Both rely on the member operations for this simple record, but they occur at different lifetime stages and can have different failure and performance characteristics.

Copy assignment of strings and vectors updates their owned values without requiring you to manually free old buffers. This does not make every compiler-generated assignment of every multi-member domain class transactional. If one member operation succeeds and a later one throws, a larger relationship between fields may need additional design. Resource ownership being managed correctly is a narrower promise than preserving every cross-member business invariant under every exception.

A const source can be copied because reading its value is sufficient. A const destination cannot ordinarily be assigned a new value after construction. That difference is useful when keeping a baseline record unchanged while experimenting with revisions. Avoid removing const from the baseline merely to make a mistaken assignment compile; identify whether the intended operation is creating a new version, replacing an existing one or deliberately mutating an alias.

C++ PRACTICE RUNNER

Trace a copied integer field and a borrowed alias

The copy changes independently, whereas the reference names the original object. This example isolates aliasing without introducing pointer ownership.

#include <iostream>
struct Counter { int value{}; };
int main() {
    Counter original{3};
    auto copy = original;
    auto& alias = original;
    copy.value = 8;
    alias.value = 5;
    std::cout << "Original: " << original.value << '\n';
    std::cout << "Copy: " << copy.value << '\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
Original: 5
Copy: 8

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

Let resource-managing members do their jobs

The Rule of Zero recommends that ordinary types whose members already manage their resources generally do not define custom destructors, copy constructors, copy assignment operators, move constructors or move assignment operators just to manage those same resources again. Our record has string and vector members, so no manual delete or hand-written copying loop is needed. Leaving those special functions alone makes the intended ownership easier to review and reduces opportunities for double cleanup.

This is not a ban on ordinary constructors, validation functions or domain methods. A class may still need a constructor to establish a nonempty identifier or a checked mutation to preserve a numeric bound. The guideline concerns responsibility for special lifetime and transfer operations, not whether all types should become unrestricted public bags of data. Prefer established owning library members, then evaluate whether their generated combinations match the domain's actual semantics.

A user-defined destructor added only to print goodbye can also affect which move operations are implicitly generated. Avoid casually changing the set of special functions just to instrument an otherwise ordinary value type. The lifetime lesson uses a separate deliberately noncopyable tracing type to demonstrate cleanup; it is not the template for every record. Later ownership lessons explain when a resource handle genuinely needs a custom resource-management type or an existing smart pointer.

An independent container does not imply independent pointees or valid views

The integers in vector<int> are values. Replacing the element type with pointers or shared owners would change what copying means for the objects they refer to. A copied container can be independent while its elements still refer to the same external resource. Do not generalize this lesson's result into a promise that every possible nested structure is deeply duplicated. Choose the representation by the ownership and identity relationships the application needs.

Similarly, copying a reference-like view does not copy the underlying text or extend its owner's lifetime. This lesson avoids string_view and pointer members so the independence argument stays simple. If you retain a reference to an element and then grow a vector, the earlier iterator lesson's invalidation rules still apply; record copying does not repair a dangling alias. Track who owns each value and when a borrowed access path can stop being valid.

Copying a large vector has work proportional to the elements it copies; the convenience of value semantics is not a claim that all copies are free. Pass a const reference when a function only needs to observe an existing record during the call. Return owned values when the interface requires an independent result. Later move semantics can reduce some transfer costs, but it should not be used to hide an unclear ownership contract or justify premature low-level memory management.

Test what remains unchanged as carefully as what changes

The lab reads an original title, a count from zero through five, that many integer reading lines in minus one hundred through one hundred, a replacement title and one appended reading in the same numeric range. Each title must contain one through forty bytes and at least one byte other than an ordinary space. This is a documented byte-level text policy, not a Unicode normalization rule. All required input must be accepted before any report is printed.

Construct the original record, copy it into a new object, replace only the copy's title and append only to its vector. Print both titles and both complete sequences with one space before each displayed integer. The original sequence may be empty, and the copy then contains only the appended value. At most six readings exist in the revised sequence, so the lab stays bounded without falsely claiming that copying arbitrary records always uses constant memory.

Test zero original readings, the five-reading maximum, negative and repeated values, both title-length endpoints, malformed counts, missing required input and an invalid appended value. Compare every original value after the edits, not just the size: overwriting an original integer could preserve its count while violating independence. As a repair exercise, deliberately declare the revision as a reference in a copy of your work, observe the unexpected original changes, then restore a real value copy.

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

Copy a record owning a title and readings, revise only the copy, and print evidence that every original value remains unchanged.

Acceptance checklist

  • Use a record with std::string and std::vector<int>, with no user-defined special lifetime functions.
  • Read original title, count 0..5, required readings -100..100, replacement title and appended reading -100..100, each on its own line.
  • Accept titles of 1–40 bytes containing something other than ordinary spaces; reject bad or missing required input as Invalid input with status 1.
  • Create a value copy, then change only its title and append one value.
  • Print Original title, Original values, Copy title and Copy values; empty sequences must not require front or back.
Need a hint?
  1. Use auto revised = original, not auto& revised = original.
  2. A title-validation helper can be shared by both input title checks.
  3. Print every sequence element with a leading space so empty output lines need no special indexing.
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 <vector>
struct Record { std::string title; std::vector<int> readings; };
bool validTitle(const std::string& title) {
    return !title.empty() && title.size() <= 40 && title.find_first_not_of(' ') != std::string::npos;
}
int main() {
    std::string title, replacement;
    int count{}, appended{};
    if (!std::getline(std::cin, title) || !validTitle(title) || !readInteger(count) || count < 0 || count > 5) {
        std::cout << "Invalid input\n"; return 1;
    }
    std::vector<int> readings;
    for (int i = 0; i < count; ++i) {
        int value{};
        if (!readInteger(value) || value < -100 || value > 100) {
            std::cout << "Invalid input\n"; return 1;
        }
        readings.push_back(value);
    }
    if (!std::getline(std::cin, replacement) || !validTitle(replacement) || !readInteger(appended) || appended < -100 || appended > 100) {
        std::cout << "Invalid input\n"; return 1;
    }
    const Record original{title, readings};
    auto revised = original;
    revised.title = replacement;
    revised.readings.push_back(appended);
    std::cout << "Original title: " << original.title << "\nOriginal values:";
    for (int value : original.readings) std::cout << ' ' << value;
    std::cout << "\nCopy title: " << revised.title << "\nCopy values:";
    for (int value : revised.readings) std::cout << ' ' << value;
    std::cout << '\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.

Expected output

Original title: Morning
Original values: 2 4
Copy title: Revised
Copy values: 2 4 8

Accepted input produces a record with ordinary owning members; no custom destructor or copy routine is needed to manage their storage.

The revision is a new value, so its subsequent string assignment and vector append do not mutate the original members.

Full sequence output checks content independence even for empty data, while validation bounds the original to five readings and the revision to six.

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.

  • Explain independent copies for a record owning string and vector members.
  • Distinguish copy construction from copy assignment.
  • Recognize aliasing through a reference as different from copying a value.
  • Apply the Rule of Zero without confusing resource safety with domain validity.
  • Test empty and nonempty copied collections without invalid access.

References and further reading

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

  • C++ Core Guidelines: Rule of Zero
  • Microsoft: copy constructors and copy assignment
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 auto revised = original do for this Record?
02How is auto& alias = original different?
03What is the Rule of Zero advising here?
04Does copying vector<int*> duplicate the pointed-to objects?
05Does Rule of Zero prove every multi-member assignment is transactional?
PREVIOUS LESSONC++ Const Member Functions and Composition
NEXT LESSONC++ Object Lifetime, Destructors and RAII
ON THIS PAGELearning objectivesA value copy represents another independently editable recordCopy construction and assignment happen at different boundariesLet resource-managing members do their jobsAn independent container does not imply independent pointees or valid viewsTest what remains unchanged as carefully as what changesIndependent labReviewKnowledge checkReferences
Course overview