Skip to content

← References, Pointers, Ownership step 3 of 4

Medium Primitives

Give an allocated object an owner

When an object needs dynamic allocation and a single owner, std::unique_ptr can manage its lifetime. The owner destroys the object when it is reset or destroyed, including when a normal return or exception leaves its scope. Ownership can also move to another unique pointer.

Dynamic allocation is a design choice. Returning an object by value or using a base-class reference does not, by itself, require a heap allocation.

std::unique_ptr<Shape> make_shape(int kind, int a, int b);  // returns ownership
void consume(std::unique_ptr<Shape> shape);                // takes ownership
void inspect(const Shape& shape);                         // borrows an object

A unique pointer can be null and cannot be copied. It has no shared reference count. Its representation depends on its pointer and deleter types, so do not assume every specialization has the same size as a raw pointer. The pointed-to object is stored separately from the unique pointer itself.

Establish ownership before checking for rejection

This pattern can leak:

Shape* shape = new Rect(a, b);
if (shape->area() <= 0) {
    return nullptr;  // shape has no owner to delete it
}
return std::unique_ptr<Shape>(shape);

Give the object an owner before taking a path that might return early:

auto shape = std::make_unique<Rect>(a, b);
if (shape->area() <= 0) {
    return nullptr;  // the local owner destroys the rejected rectangle
}
return shape;       // transfers ownership to the returned pointer

make_unique allocates and constructs the object, then returns its owner. It does not construct the object inside the unique pointer. Direct construction such as std::unique_ptr<Rect>(new Rect(a, b)) also establishes ownership in one expression; the starter's bug is the separate rejection branch before adoption. Prefer make_unique for ordinary construction. Adopting an existing allocation or using a custom deleter can require a different constructor.

Observing, resetting and transferring

Operation Effect
p.get() Borrow the stored pointer without changing ownership
p.reset() Destroy the owned object, if any, and become null
p.release() Return the pointer and become null without destroying the object
auto q = std::move(p); Move ownership into q; p becomes null

std::move(p) alone is a cast; the receiving move operation transfers ownership. Do not delete a pointer obtained from get() while its unique pointer still owns it. After release(), arrange a new owner or the correct cleanup operation; that may be a custom deleter rather than delete.

This task returns derived objects through std::unique_ptr<Shape> using its default deleter. Shape therefore has a virtual destructor so deleting through the base pointer also destroys the derived object correctly.

Your task

std::unique_ptr<Shape> make_shape(int kind, int a, int b);
Kind Construct
1 Rect(a, b), whose area is a * b
2 Square(a), whose area is a * a; ignore b
Any other value Return nullptr

Reject a constructed shape if its area is zero or negative. The rule checks the computed area, not each dimension: for example, Square(-3) has area 9 and is accepted. Inputs keep the area calculations within the range of int.

Leave Shape, its derived classes and audit unchanged. The provided audit function reports one area per specification (-1 for rejection), followed by how many accepted shapes meet its threshold. The harness then reports Shape::live after all local owners have been destroyed. It must be zero, including when a shape is rejected.