Skip to content

CT 301 Homework 2

CT 301 · C++ Fundamentals

Homework 2:
Building ct::Vector

Manual memory, and the container that uses it
Part 1

The ideas first

A handful of memory concepts. Get these and the homework is mostly mechanical.

Why this matters

Now you manage the memory yourself

  • HW1 used unique_ptr and RAII, the library freed things for you.
  • HW2 you build a container that owns a raw buffer and manages every element by hand.
  • The goal: feel what the library does for you, so you understand and trust it.

Raw new/delete were banned in HW1 on purpose. HW2 is where you finally use raw memory.

The core idea

Capacity is not size

7392size = 4 (constructed)capacity = 10 (room in the buffer)

capacity ≥ size, always. The slots past size_ are memory with no object in them yet.

Two tools

new[] can't do what a vector needs

new[] / delete[]

Allocates and constructs all n at once. No empty, reserved slots.

The decoupled form

Allocate raw bytes once, construct elements one at a time as you grow.

So the vector splits allocation from construction. The next slides are the four tools.

Steps 1 and 4

Raw bytes: ::operator new / delete

  • ::operator new(bytes) gives raw memory, no constructors run.
  • ::operator delete(p) frees it, no destructors run.
void* raw = ::operator new(sizeof(T) * capacity);
T* buf = static_cast<T*>(raw);   // bytes, not objects yet
::operator delete(buf);            // frees bytes only
Step 2

Construct in place: placement new

739construct here (at size_)
new (elements_ + size_) T(value);   // build one element at the end
++size_;
Step 3

Destroy by hand

  • p->~T() runs the destructor without freeing memory.
  • std::destroy_n(buf, n) and std::destroy_at(p) do a range.
  • Every placement-new you do, you owe one destroy, or resources leak.
Put it together

Allocate, construct, destroy, free

1. Allocate::operator new2. Constructplacement new3. Destroy~T() / destroy_n4. Free::operator deleteSteps 2 and 3 repeat for every element. Steps 1 and 4 happen only when the buffer changes.
Don't hand-roll loops

The uninitialized-memory helpers

  • std::uninitialized_copy, uninitialized_fill_n, uninitialized_move_n, uninitialized_value_construct_n: construct a range into raw memory.
  • std::destroy_n, std::destroy_at: destroy a range.

These are placement-new and destroy over a whole range, with exception safety built in. Use them.

Grow by moving

reserve: move to a bigger buffer

old buffer (capacity 4, full)abcdmovenew buffer (capacity 8)abcdthen destroy old elements + ::operator delete the old buffer

Allocate bigger, move the elements over, destroy the old ones, free the old buffer.

std::move

Why move instead of copy

source adest cthe buffersteal the pointera is left empty (size 0)

A move steals the internals and leaves the source empty. For a string, that is a pointer swap, not a full copy.

std::rotate

insert places an element by rotating

abcd1. push_back x at the endabcdxrotateabxcd2. rotate it into position

Build it at the end where it is easy, then rotate it back to where it belongs.

noexcept & static_cast

Two small things that matter

noexcept

Mark move ctor, move assignment, and swap noexcept. That promise lets the vector move (not copy) while growing.

static_cast

Raw to typed: static_cast<T*>(raw). Iterator to index: static_cast<size_type>(pos - begin()).

using & const

Type aliases and const-correctness

  • Publish the names the STL expects: value_type, size_type, iterator, const_iterator, reference, and the rest.
  • Provide const overloads of operator[], at, begin/end: a const vector is still readable.
Watch out for these

The classic memory bugs

Shallow copy (the bug)vector avector bone bufferboth free it double-freeDeep copy (correct)vector avector bbuffer Abuffer Bindependent safe

Also: leaks (freed bytes but forgot to destroy), off-by-one between size_ and capacity_.

Part 2

Now the homework

The class, the order to build it, the grade, and the dates.

The assignment

A small std::vector, by hand

  • Implement ct::Vector<T>: a single header, namespace ct, guard CT_VECTOR_H.
  • It owns a raw buffer and manages every element's lifetime.
  • You also write a small driver that uses it.
Three members

Everything hangs off three fields

ct::Vectorelements_T*size_4capacity_8the buffer it owns

Every function's job is to keep these three consistent.

Do it in this order

Build the memory core first

  • 1. allocate, deallocate, grow_if_needed, reserve.
  • 2. default ctor, push_back, size/capacity, the destructor.
  • 3. access (operator[], at), iterators (begin/end).
  • 4. copy/move, then the rest of the modifiers.

Test after each step. Do not write the whole class and then compile once.

Where the points are

The memory core

  • allocate / deallocate: the raw buffer via ::operator new / delete.
  • grow_if_needed: the capacity policy. reserve: move to a bigger buffer. shrink_to_fit: trim.
T* allocate(size_type n){ return static_cast<T*>(::operator new(sizeof(T)*n)); }
void deallocate(){ ::operator delete(elements_); }   // bytes only
The growth policy

grow_if_needed

  • If size_ + n still fits in capacity_, do nothing.
  • Otherwise reserve a larger capacity, typically double (or a small start if empty).

Doubling is what makes a long run of push_backs cheap on average.

Lifecycle & rule of 5/7

Constructors, destructor, copy, move

  • Constructors: default, count, count-and-value, initializer-list, range, copy, move.
  • ~Vector(): clear() then deallocate(), destroy elements, then free bytes.
  • Copy = deep copy. Move = steal the buffer, leave source empty, noexcept.
A clean trick

Copy-and-swap

Vector& operator=(const Vector& other){
  Vector temp(other);   // deep-copy into a temporary
  swap(temp);           // take its buffer; temp frees the old one
  return *this;
}

Self-assignment just works, and you reuse the copy constructor and swap.

Reading elements

operator[] vs at(), and iterators

begin()end()end() points one past the last element, so begin()..end() is the range.

operator[] is unchecked; at() throws std::out_of_range. Iterators are just T*.

Modifiers

Adding and removing

  • At the end: push_back/emplace_back (grow, then construct), pop_back (destroy last), clear (destroy all, capacity kept).
  • In the middle: insert/emplace via std::rotate; erase moves the tail down; resize grows or shrinks.

emplace_back forwards its arguments and builds the element in place.

The driver

driver.cpp is required

  • A small program that fills a vector and computes a total() over the data.
  • It is part of the grade (6 points), not extra credit. Submit it.

It is also your own proof the vector works end to end.

Read this twice

What is not allowed

  • No std::vector, no smart pointers. The point is to do the memory yourself.

Using the thing you are supposed to build is an automatic zero on the core.

How to run it

Build with the sanitizers on

  • Use the make targets; flags include -fsanitize=address,undefined.
  • Test small first. A [FAIL] is a wrong value; a sanitizer abort or segfault is a memory bug.
  • Don't wrap a segfault in try/catch, it is not catchable.
100 points

Where the points go, and what to submit

  • Memory core 47 · cross-cutting 13 · modifiers 10 · construction 9
  • Driver 6 · access 4 · iterators 4 · assign/swap/== 4 · capacity 3
  • Submit ctvector.h + driver.cpp via Canvas. Build clean, 0 warnings.

Memory core plus cross-cutting is well over half. That is where to spend your time.

Mark your calendar

Dates

  • HW2 due June 29, 11:59pm Colorado time. One day late = 10% (June 30).
  • Exam 2: July 2.
  • Optional re-upload of your lower HW1/HW2 grade as "HW3": July 6 (no late).
  • Optional HW2 extra credit (posted later): July 7 (no late).
  • Final: July 10, 9am to 9pm Colorado time.
Start here

Build the memory core first.
Build small, test small.

Sanitizers on from line one. Questions to office hours or Canvas.