Chapter 23 Advanced 53 Questions

Practice Questions — Smart Pointers and Modern Memory Management

← Back to Notes
10 Easy
12 Medium
10 Hard

Topic-Specific Questions

Question 1
Easy
What is the output?
auto p = make_unique<int>(42);
cout << *p;
make_unique creates a unique_ptr. Dereference with * to get the value.
42
Question 2
Easy
What is the output?
auto p = make_shared<int>(99);
cout << p.use_count();
A freshly created shared_ptr has a use_count of 1.
1
Question 3
Easy
What is the output?
auto p1 = make_shared<int>(10);
auto p2 = p1;
auto p3 = p1;
cout << p1.use_count();
Each copy of shared_ptr increments the reference count.
3
Question 4
Easy
What is the output?
unique_ptr<int> p1 = make_unique<int>(5);
unique_ptr<int> p2 = move(p1);
cout << (p1 == nullptr) << " " << *p2;
After move, the source unique_ptr becomes nullptr.
1 5
Question 5
Easy
What is the output?
auto p = make_unique<int>(7);
cout << (p ? "valid" : "null");
p.reset();
cout << " " << (p ? "valid" : "null");
reset() releases the managed object and sets the pointer to nullptr.
valid null
Question 6
Medium
What is the output?
auto p1 = make_shared<int>(50);
{
    auto p2 = p1;
    cout << p1.use_count() << " ";
}
cout << p1.use_count() << " " << *p1;
When p2 goes out of scope, use_count decreases.
2 1 50
Question 7
Medium
What is the output?
auto sp = make_shared<int>(100);
weak_ptr<int> wp = sp;
cout << wp.use_count() << " " << wp.expired() << " ";
sp.reset();
cout << wp.use_count() << " " << wp.expired();
weak_ptr does not affect use_count. expired() returns true when the object no longer exists.
1 0 0 1
Question 8
Medium
What is the output?
auto sp = make_shared<int>(77);
weak_ptr<int> wp = sp;
auto locked = wp.lock();
cout << (locked ? to_string(*locked) : "null") << " ";
cout << sp.use_count();
lock() returns a shared_ptr, incrementing use_count.
77 2
Question 9
Medium
What is the output?
struct X {
    int val;
    X(int v) : val(v) { cout << "X(" << val << ") ";
    }
    ~X() { cout << "~X(" << val << ") "; }
};

{
    auto p = make_unique<X>(1);
    auto q = make_unique<X>(2);
}
cout << "done";
Destructors run in reverse order of construction when leaving scope.
X(1) X(2) ~X(2) ~X(1) done
Question 10
Hard
What is the output?
auto p = make_unique<int>(10);
int* raw = p.get();
cout << *raw << " ";
p.reset();
cout << (p == nullptr);
get() returns the raw pointer without releasing ownership. After reset(), p is null.
10 1
Question 11
Hard
What is the output?
auto p = make_shared<int>(55);
auto q = move(p);
cout << (p == nullptr) << " " << q.use_count() << " " << *q;
Moving a shared_ptr transfers ownership without changing use_count.
1 1 55
Question 12
Hard
What is the output?
class A {
public:
    A() { cout << "A "; }
    ~A() { cout << "~A "; }
};

vector<unique_ptr<A>> v;
v.push_back(make_unique<A>());
v.push_back(make_unique<A>());
cout << "clear ";
v.clear();
Clearing a vector of unique_ptrs destroys all managed objects.
A A clear ~A ~A
Question 13
Easy
What is the output?
auto p = make_shared<int>(25);
auto q = p;
auto r = p;
r.reset();
cout << p.use_count() << " " << *p;
reset() releases one owner. Others remain.
2 25
Question 14
Medium
What is the output?
auto p = make_unique<int>(100);
auto q = make_unique<int>(200);
p.swap(q);
cout << *p << " " << *q;
swap exchanges the managed pointers between two unique_ptrs.
200 100
Question 15
Medium
What is the output?
weak_ptr<int> wp;
{
    auto sp = make_shared<int>(42);
    wp = sp;
    cout << wp.expired() << " ";
}
cout << wp.expired();
When the last shared_ptr is destroyed, the weak_ptr expires.
0 1
Question 16
Hard
What is the output?
struct Obj {
    int val;
    Obj(int v) : val(v) { cout << "C" << v << " "; }
    ~Obj() { cout << "D" << val << " "; }
};
auto p = make_unique<Obj>(1);
p = make_unique<Obj>(2);
cout << "end ";
Assigning a new unique_ptr destroys the old managed object first.
C1 C2 D1 end D2
Question 17
Easy
What is the output?
auto p = make_shared<int>(10);
auto q = make_shared<int>(20);
cout << (p == q) << " ";
q = p;
cout << (p == q) << " " << *q;
Smart pointer comparison checks if they point to the same object.
0 1 10
Question 18
Hard
What is the output?
auto p = make_unique<int>(50);
int* raw = p.release();
cout << (p == nullptr) << " " << *raw;
delete raw;  // Must manually delete after release!
release() transfers ownership to the raw pointer and sets unique_ptr to null.
1 50

Mixed & Application Questions

Question 1
Easy
What is the output?
auto p = make_unique<string>("Hello");
cout << p->size() << " " << (*p)[0];
unique_ptr uses -> to access the managed object's members.
5 H
Question 2
Easy
What is the output?
shared_ptr<int> p1 = make_shared<int>(10);
shared_ptr<int> p2 = p1;
*p2 = 20;
cout << *p1;
p1 and p2 point to the same int.
20
Question 3
Medium
What is the output?
auto p1 = make_unique<int>(100);
auto p2 = make_unique<int>(200);
swap(p1, p2);
cout << *p1 << " " << *p2;
swap exchanges the managed pointers.
200 100
Question 4
Medium
What is the output?
unique_ptr<int[]> arr = make_unique<int[]>(4);
for (int i = 0; i < 4; i++) arr[i] = (i + 1) * 10;
cout << arr[0] << " " << arr[3];
unique_ptr supports array subscript operator [].
10 40
Question 5
Medium
What is the output?
auto sp = make_shared<int>(30);
weak_ptr<int> wp = sp;
sp = make_shared<int>(60);
auto locked = wp.lock();
cout << (locked ? "alive" : "dead");
After sp is reassigned, what happens to the original int(30)?
dead
Question 6
Hard
What is the output?
struct Node {
    int val;
    unique_ptr<Node> next;
    Node(int v) : val(v), next(nullptr) {}
};

auto head = make_unique<Node>(1);
head->next = make_unique<Node>(2);
head->next->next = make_unique<Node>(3);

auto curr = head.get();
while (curr) {
    cout << curr->val << " ";
    curr = curr->next.get();
}
get() returns a raw non-owning pointer for traversal.
1 2 3
Question 7
Hard
What is the output?
class B {
public:
    B() { cout << "B "; }
    B(const B&) { cout << "Bc "; }
    B(B&&) noexcept { cout << "Bm "; }
    ~B() { cout << "~B "; }
};

B b1;
B b2 = b1;
B b3 = move(b1);
Copy constructor is called for b2, move constructor for b3.
B Bc Bm ~B ~B ~B
Question 8
Medium
What is RAII and why is it fundamental to modern C++?
Think about tying resource lifetime to object lifetime.
RAII (Resource Acquisition Is Initialization) means that resources (memory, files, locks, sockets) are acquired in a constructor and released in a destructor. Since C++ guarantees destructor execution when objects go out of scope (including during stack unwinding from exceptions), RAII ensures resources are always properly cleaned up without explicit cleanup code. Smart pointers are the most common example of RAII.
Question 9
Hard
When should you use unique_ptr vs shared_ptr vs weak_ptr?
Think about ownership semantics.
Use unique_ptr when there is a single clear owner of the resource (the most common case). Use shared_ptr when multiple parts of the code need to share ownership and the resource should only be deleted when all owners are done. Use weak_ptr to observe a shared resource without preventing its deletion, or to break circular references between shared_ptrs.
Question 10
Hard
What is the difference between the Rule of Three, Rule of Five, and Rule of Zero?
Think about which special member functions you need to define.
Rule of Three (C++03): if you define any of destructor, copy constructor, or copy assignment, define all three. Rule of Five (C++11): adds move constructor and move assignment to the three. Rule of Zero: prefer using types that manage their own resources (smart pointers, std::string, std::vector) so you do not need to define any of the five. Modern C++ strongly favors the Rule of Zero.
Question 11
Medium
What is the output?
auto p = make_shared<vector<int>>(vector<int>{10, 20, 30});
cout << p->size() << " " << (*p)[1];
shared_ptr to a vector. Use -> for member access.
3 20
Question 12
Hard
What is the output?
auto sp = make_shared<int>(10);
weak_ptr<int> wp = sp;
auto sp2 = wp.lock();
cout << sp.use_count() << " ";
sp.reset();
sp2.reset();
cout << wp.expired();
lock() creates a new shared_ptr. Both sp and sp2 must be reset for the object to be deleted.
2 1
Question 13
Easy
What is the output?
unique_ptr<int> p;
cout << (p == nullptr) << " ";
p = make_unique<int>(99);
cout << (p == nullptr) << " " << *p;
A default-constructed unique_ptr is nullptr.
1 0 99
Question 14
Medium
What is the output?
struct Widget {
    int id;
    Widget(int i) : id(i) {}
};
auto w = make_unique<Widget>(42);
cout << w->id << " ";
w->id = 100;
cout << w->id;
unique_ptr provides -> for accessing members of the managed object.
42 100

Multiple Choice Questions

MCQ 1
Which header provides unique_ptr, shared_ptr, and weak_ptr?
  • A. <pointer>
  • B. <memory>
  • C. <smart_ptr>
  • D. <utility>
Answer: B
B is correct. All smart pointers are defined in the <memory> header.
MCQ 2
What does RAII stand for?
  • A. Runtime Automatic Initialization Interface
  • B. Resource Acquisition Is Initialization
  • C. Reference And Iterator Integration
  • D. Random Access Is Implemented
Answer: B
B is correct. RAII ties resource lifetime to object lifetime. Resources are acquired in constructors and released in destructors.
MCQ 3
Can you copy a unique_ptr?
  • A. Yes, like any other object
  • B. No, but you can move it with std::move
  • C. Yes, but only within the same scope
  • D. No, and you cannot move it either
Answer: B
B is correct. unique_ptr has a deleted copy constructor. It can only be moved, which transfers exclusive ownership to the destination.
MCQ 4
What does use_count() return for a shared_ptr?
  • A. The number of bytes allocated
  • B. The number of shared_ptr instances sharing ownership
  • C. The number of times the pointer was dereferenced
  • D. The memory address as an integer
Answer: B
B is correct. use_count() returns the number of shared_ptr objects that share ownership of the managed resource.
MCQ 5
What is the preferred way to create a unique_ptr?
  • A. unique_ptr<int> p = new int(42);
  • B. auto p = make_unique<int>(42);
  • C. unique_ptr<int> p(42);
  • D. auto p = unique_ptr<int>(42);
Answer: B
B is correct. make_unique is exception-safe and clearly expresses intent. Never use raw new with smart pointers.
MCQ 6
What does weak_ptr::lock() return?
  • A. A raw pointer
  • B. A unique_ptr
  • C. A shared_ptr (or empty shared_ptr if expired)
  • D. A boolean indicating if the object exists
Answer: C
C is correct. lock() returns a shared_ptr that shares ownership with the existing shared_ptrs. If the managed object has been deleted, it returns an empty shared_ptr.
MCQ 7
What is the primary purpose of weak_ptr?
  • A. To be faster than shared_ptr
  • B. To replace unique_ptr in multi-threaded code
  • C. To break circular references between shared_ptrs
  • D. To provide array access for smart pointers
Answer: C
C is correct. weak_ptr does not increment the reference count, breaking cycles that would otherwise prevent shared_ptrs from ever reaching use_count 0.
MCQ 8
What happens to a unique_ptr after std::move?
  • A. It still points to the same object
  • B. It becomes nullptr
  • C. It is deleted
  • D. It points to a copy of the object
Answer: B
B is correct. After std::move, the source unique_ptr becomes nullptr. Ownership is transferred to the destination.
MCQ 9
What is the advantage of make_shared over using new with shared_ptr?
  • A. make_shared is slower but more readable
  • B. make_shared performs a single allocation (object + control block together)
  • C. make_shared allows custom deleters
  • D. make_shared creates a unique_ptr internally
Answer: B
B is correct. make_shared allocates the control block (reference counts) and the object in a single heap allocation, improving cache locality and performance.
MCQ 10
What does p.get() return for a smart pointer?
  • A. A copy of the smart pointer
  • B. The raw pointer without transferring ownership
  • C. The reference count
  • D. A weak_ptr to the managed object
Answer: B
B is correct. get() returns the raw pointer managed by the smart pointer. The smart pointer retains ownership. Use this for interoperability with APIs that require raw pointers.
MCQ 11
What is an rvalue reference (&&) used for?
  • A. Double indirection (pointer to pointer)
  • B. Logical AND of two references
  • C. Binding to temporary objects to enable move semantics
  • D. Creating constant references
Answer: C
C is correct. Rvalue references (&&) bind to temporaries and moved-from objects, enabling move constructors and move assignment operators to steal resources instead of copying.
MCQ 12
What does the Rule of Zero state?
  • A. Never define any constructors
  • B. Use zero-cost abstractions only
  • C. Prefer types that manage their own resources so you need no custom destructor, copy, or move operations
  • D. Always initialize variables to zero
Answer: C
C is correct. The Rule of Zero says: if your class uses smart pointers and other RAII types for all resources, the compiler-generated defaults are correct and you need zero special member functions.
MCQ 13
What happens if you create two shared_ptrs from the same raw pointer?
  • A. They share the same control block
  • B. Double deletion -- undefined behavior
  • C. One becomes a weak_ptr automatically
  • D. A compilation error
Answer: B
B is correct. Each shared_ptr creates its own control block, both thinking they are the sole owner. When both go out of scope, they both try to delete the same pointer, causing undefined behavior (double free).
MCQ 14
What does std::move actually do?
  • A. Moves the object to a different memory location
  • B. Deletes the source object
  • C. Casts the argument to an rvalue reference, enabling the move constructor/assignment to be called
  • D. Creates a deep copy
Answer: C
C is correct. std::move is just a cast to T&&. It does not move anything by itself. The actual moving happens in the move constructor or move assignment operator that gets selected because of the rvalue reference.
MCQ 15
What does p.reset() do on a unique_ptr?
  • A. Resets the value to zero
  • B. Deletes the managed object and sets the pointer to nullptr
  • C. Transfers ownership to another pointer
  • D. Resets the reference count to 1
Answer: B
B is correct. reset() deletes the currently managed object and sets the smart pointer to nullptr. You can also pass a new raw pointer to reset(new_ptr) to manage a different object.
MCQ 16
Why should you mark move constructors and move assignment operators as noexcept?
  • A. It makes them faster
  • B. STL containers use move only if the move operation is noexcept; otherwise they fall back to copy for exception safety
  • C. It is required by the standard
  • D. It prevents the function from being called
Answer: B
B is correct. Containers like vector use move during reallocation only if the move constructor is noexcept. If it might throw, the container falls back to copying to maintain the strong exception guarantee.

Coding Challenges

Challenge 1: Build a Unique Pointer Linked List

Easy
Create a singly linked list using unique_ptr where each node owns the next node. Insert 5 values and traverse the list printing each value.
Sample Input
Values: 10, 20, 30, 40, 50
Sample Output
10 -> 20 -> 30 -> 40 -> 50 -> null
Use unique_ptr<Node> for the next pointer. No raw new/delete.
#include <iostream>
#include <memory>
using namespace std;

struct Node {
    int val;
    unique_ptr<Node> next;
    Node(int v) : val(v), next(nullptr) {}
};

int main() {
    auto head = make_unique<Node>(10);
    auto curr = head.get();
    for (int v : {20, 30, 40, 50}) {
        curr->next = make_unique<Node>(v);
        curr = curr->next.get();
    }
    curr = head.get();
    while (curr) {
        cout << curr->val << " -> ";
        curr = curr->next.get();
    }
    cout << "null" << endl;
    return 0;
}

Challenge 2: Shared Resource Counter

Medium
Create a shared_ptr to an integer. Pass it to 3 different functions that each store a copy. Print use_count after each step and verify it returns to 1 after all functions complete.
Sample Input
Initial value: 42
Sample Output
Created: count=1 After share1: count=2 After share2: count=3 After share3: count=4 All released: count=1 Value: 42
Use shared_ptr. Demonstrate reference counting.
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

int main() {
    auto p = make_shared<int>(42);
    cout << "Created: count=" << p.use_count() << endl;

    vector<shared_ptr<int>> holders;
    for (int i = 1; i <= 3; i++) {
        holders.push_back(p);
        cout << "After share" << i << ": count=" << p.use_count() << endl;
    }
    holders.clear();
    cout << "All released: count=" << p.use_count() << endl;
    cout << "Value: " << *p << endl;
    return 0;
}

Challenge 3: Factory Function Returning unique_ptr

Medium
Create a factory function that returns different shapes (Circle, Rectangle) as unique_ptr. Use polymorphism to call area() on each shape.
Sample Input
Circle(5.0), Rectangle(4.0, 6.0)
Sample Output
Circle area: 78.5398 Rectangle area: 24
Use unique_ptr for ownership. Virtual functions for polymorphism.
#include <iostream>
#include <memory>
#include <cmath>
using namespace std;

class Shape {
public:
    virtual double area() const = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
    double r;
public:
    Circle(double radius) : r(radius) {}
    double area() const override { return M_PI * r * r; }
};

class Rectangle : public Shape {
    double w, h;
public:
    Rectangle(double width, double height) : w(width), h(height) {}
    double area() const override { return w * h; }
};

unique_ptr<Shape> createShape(const string& type, double a, double b = 0) {
    if (type == "circle") return make_unique<Circle>(a);
    if (type == "rectangle") return make_unique<Rectangle>(a, b);
    return nullptr;
}

int main() {
    auto c = createShape("circle", 5.0);
    auto r = createShape("rectangle", 4.0, 6.0);
    cout << "Circle area: " << c->area() << endl;
    cout << "Rectangle area: " << r->area() << endl;
    return 0;
}

Challenge 4: Break a Circular Reference with weak_ptr

Hard
Create a Person class where each person can have a best friend. First demonstrate the memory leak with shared_ptr (circular reference), then fix it using weak_ptr.
Sample Input
Arjun's best friend is Priya, Priya's best friend is Arjun
Sample Output
Arjun created Priya created Arjun's friend: Priya Priya destroyed Arjun destroyed
Use weak_ptr for the bestFriend pointer. Demonstrate lock() for safe access.
#include <iostream>
#include <memory>
using namespace std;

class Person {
public:
    string name;
    weak_ptr<Person> bestFriend;  // weak_ptr breaks the cycle
    Person(string n) : name(n) { cout << name << " created" << endl; }
    ~Person() { cout << name << " destroyed" << endl; }
    void showFriend() {
        if (auto f = bestFriend.lock())
            cout << name << "'s friend: " << f->name << endl;
        else
            cout << name << " has no friend" << endl;
    }
};

int main() {
    auto arjun = make_shared<Person>("Arjun");
    auto priya = make_shared<Person>("Priya");
    arjun->bestFriend = priya;
    priya->bestFriend = arjun;
    arjun->showFriend();
    return 0;
}

Challenge 5: Implement a Simple Resource Pool with shared_ptr

Hard
Create a simple connection pool that hands out shared_ptr objects. Track how many connections are active using use_count. When all shared_ptrs to a connection are released, the connection returns to the pool.
Sample Input
Get 2 connections, release 1, check active count
Sample Output
Connection 1 created Connection 2 created Active connections: 2 Releasing connection 2 Active connections: 1
Use shared_ptr and weak_ptr. No raw pointers.
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

class Connection {
    int id;
public:
    Connection(int i) : id(i) { cout << "Connection " << id << " created" << endl; }
    ~Connection() { cout << "Connection " << id << " destroyed" << endl; }
    int getId() const { return id; }
};

class Pool {
    vector<weak_ptr<Connection>> connections;
    int nextId = 1;
public:
    shared_ptr<Connection> getConnection() {
        auto conn = make_shared<Connection>(nextId++);
        connections.push_back(conn);
        return conn;
    }
    int activeCount() {
        int count = 0;
        for (auto& wp : connections)
            if (!wp.expired()) count++;
        return count;
    }
};

int main() {
    Pool pool;
    auto c1 = pool.getConnection();
    {
        auto c2 = pool.getConnection();
        cout << "Active connections: " << pool.activeCount() << endl;
        cout << "Releasing connection 2" << endl;
    }
    cout << "Active connections: " << pool.activeCount() << endl;
    return 0;
}

Need to Review the Concepts?

Go back to the detailed notes for this chapter.

Read Chapter Notes

Want to learn C++ with a live mentor?

Explore our C++ Masterclass