Skip to content

Lab 6 Assignment

CT 301: C++ Fundamentals · Summer 2026
Lab 6: Final Exam Prep (recommended)
Source: learncpp.com · Instructor: Dr. Francisco R. Ortega

Overview

This is an explore-and-reflect lab, like Labs 3 and 4. The goal is to learn by trying things, not to finish a polished homework. You will read a few short lessons, then run and change three small programs that cover the ideas the final emphasizes most: owning memory, copying vs moving, polymorphism, and how a growable container behaves. When you are done, you record a short video showing what you tried and explaining what you learned. There is no written report.

A note on style: the lecture slides use import std;. In this class, and in these labs, we use #include (for example #include <iostream>). Both are valid modern C++.
Why this matters for the final. The final is cumulative. Homework 1 was about owning objects safely with unique_ptr and using them through a base class (polymorphism); Homework 2 was about owning a raw buffer by hand (deep copy, move, growth). This lab puts those same ideas in three short programs so you can compare them directly, which is what the transfer questions ask about.

Where to run the code. You do not need anything installed. Paste each program into an online compiler and press Run: godbolt.org · onlinegdb.com · wandbox.org. To watch memory step by step, pythontutor.com (C++) is useful.

Reading

Read these lessons on learncpp.com. If a lesson ends with a Quiz time, do it and check the provided solution.

Lesson 19.1: Dynamic memory allocation with new and delete
Asking the free store for memory and giving it back.
Lesson 15.4: Introduction to destructors
How cleanup runs automatically when an object goes away (RAII).
Lesson 21.13: Shallow vs. deep copying
Why a class that owns memory needs its own copy, not a shared pointer.
Lesson 22.3: Move constructors and move assignment
Stealing resources instead of copying them, and what std::move does.
Lesson 22.5: std::unique_ptr
The single-owner smart pointer from Homework 1: it frees automatically and cannot be copied.
Lesson 13.13: Class templates
Writing one class that works for many types, like your Vector<T>.
Lesson 25.2: Virtual functions and polymorphism
Calling the right function through a base-class pointer, as in the Homework 1 item hierarchy.

Explore 1: two ways to own memory (rule of zero vs rule of five)

This is the central idea of the course and the most common transfer question. Owns holds a raw int buffer, so it defines its own copy and move (the rule of five). Wraps holds a std::vector, which already manages itself, so it needs none (the rule of zero). Predict what the program prints, then run it to check.

#include <iostream>
#include <vector>
#include <utility>

// Owns a raw buffer by hand -> rule of five (like your ct::Vector).
class Owns {
    int* data_;
    std::size_t size_;
public:
    Owns(std::size_t n) : data_(new int[n]), size_(n) {
        for (std::size_t i = 0; i < n; ++i) data_[i] = int(i);
    }
    ~Owns() { delete[] data_; }                 // frees the buffer (RAII)

    // deep-copy constructor: allocate a NEW buffer and copy the elements,
    // so the copy does not share this object's buffer.
    Owns(const Owns& other)
        : data_(new int[other.size_]), size_(other.size_) {
        for (std::size_t i = 0; i < size_; ++i) data_[i] = other.data_[i];
    }
    // move constructor: take other's pointer and size, then null out
    // other.data_ so its destructor does not free your buffer.
    Owns(Owns&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr; other.size_ = 0;
    }

    int&  operator[](std::size_t i) { return data_[i]; }
    int*  data() const { return data_; }
    std::size_t size() const { return size_; }
};

// Owns nothing raw -> rule of zero (like HW1 Inventory).
class Wraps {
    std::vector<int> v_;
public:
    Wraps(std::size_t n) : v_(n) {}
    // no destructor, copy, or move written by hand - the compiler's are correct
};

int main() {
    Owns a(3);
    Owns b = a;            // deep copy: b gets its own buffer
    b[0] = 99;
    std::cout << "a[0]=" << a[0] << "  b[0]=" << b[0] << "\n";
    std::cout << "same buffer? " << (a.data() == b.data()) << "\n";

    Owns c = std::move(a); // move: steals the buffer, leaves a empty
    std::cout << "c[1]=" << c[1] << "\n";

    Wraps w1(5);
    Wraps w2 = w1;         // just works - no special members needed
    std::cout << "Wraps copied fine\n";
    return 0;
}

You should see a[0]=0 (unchanged), b[0]=99, and same buffer? 0 - the copy is independent, and the move gives c the buffer. Wraps needed nothing because its std::vector member already owns and copies itself.

Explore 2: polymorphism through a base-class pointer

This is a small version of the Homework 1 item hierarchy. The items are held as unique_ptr<Item> and used through the base class. Predict the total, then run it.

#include <iostream>
#include <memory>
#include <vector>

struct Item {
    virtual double value() const = 0;     // pure virtual: every item defines it
    virtual ~Item() = default;            // virtual destructor
};
struct Equipment : Item {
    double base;
    Equipment(double b) : base(b) {}
    double value() const override { return base; }
};
struct Borrowed : Item {
    double value() const override { return 0.0; }   // not ours -> no value
};

int main() {
    std::vector<std::unique_ptr<Item>> items;
    items.push_back(std::make_unique<Equipment>(1000.0));
    items.push_back(std::make_unique<Borrowed>());

    double total = 0;
    for (const auto& p : items)           // p is a unique_ptr<Item>
        total += p->value();              // which value() runs?
    std::cout << "total = " << total << "\n";
    return 0;
}

You should see total = 1000: the call dispatches to each item's own value(). Item is abstract, so it cannot be built directly.

Explore 3: size, capacity, and growth

Your ct::Vector keeps a buffer with more room than it is using. std::vector does the same, so you can watch it. Run this and read the numbers.

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v;                    // starts empty
    for (int i = 0; i < 20; ++i) {
        v.push_back(i);
        std::cout << "size=" << v.size()
                  << " cap=" << v.capacity() << "\n";
    }
    std::vector<int> w;
    w.reserve(50);                        // capacity only
    std::cout << "after reserve: size=" << w.size()
              << " cap=" << w.capacity() << "\n";
    w.resize(5);                          // element count
    std::cout << "after resize:  size=" << w.size()
              << " cap=" << w.capacity() << "\n";
    return 0;
}

You should see capacity jump (not increase by one) as the buffer fills, size grow by one each push, reserve change capacity but not size, and resize change the number of elements.

Record your reflection

When you have run and changed all three programs, record a short video (a few minutes is fine) and submit an unlisted YouTube link. In the video:

Grading. Full credit is for a genuine attempt that shows the code you ran and explains the ideas in your own words. A low-effort submission - no face on camera, reading the code without explaining it, skipping explores, or only a few seconds long - will receive a lower grade or a zero.

There is no written report. Think about the answers before you record.