Katkat

Implement and Explain a C++ Smart Pointer

NVIDIASoftware Engineer · Technical Screen

Last updated

Tests understanding of C++ memory management, RAII, explicit ownership semantics, and the Rule of Five by implementing a reference-counted shared smart pointer.

Problem Statement

In systems programming and high-performance runtimes, robust resource management is critical to prevent memory leaks and dangling pointers. Your task is to implement a simplified, generic shared ownership smart pointer class template, SharedPtr, modeled after C++'s std::shared_ptr.

Your implementation should track references using a heap-allocated reference counter (control block) so that the managed object is destroyed and its memory freed exactly when the last owning SharedPtr is destroyed or reset.

Requirements

Implement the SharedPtr template with the following interface:

template 
class SharedPtr {
public:
    // Explicit constructor taking a raw pointer (defaults to nullptr)
    explicit SharedPtr(T* ptr = nullptr);

    // Destructor
    ~SharedPtr();

    // Copy semantics (Rule of Five)
    SharedPtr(const SharedPtr& other);
    SharedPtr& operator=(const SharedPtr& other);

    // Move semantics
    SharedPtr(SharedPtr&& other) noexcept;
    SharedPtr& operator=(SharedPtr&& other) noexcept;

    // Observers & Dereference operators
    T& operator*() const;
    T* operator->() const;
    T* get() const;
    size_t use_count() const;

    // Modifier
    void reset(T* ptr = nullptr);
};

Example Usage

SharedPtr sp1(new int(42));
assert(sp1.use_count() == 1);
assert(*sp1 == 42);

{
    SharedPtr sp2 = sp1;
    assert(sp1.use_count() == 2);
    assert(sp2.use_count() == 2);
    *sp2 = 100;
}

// sp2 went out of scope
assert(sp1.use_count() == 1);
assert(*sp1 == 100);

sp1.reset();
assert(sp1.use_count() == 0);
assert(sp1.get() == nullptr);

Edge Cases to Consider

  • Self-assignment: Handling cases like sp = sp; without prematurely freeing memory.
  • Null pointers: SharedPtr(nullptr) should initialize cleanly without allocating or incrementing counts unnecessarily, or must handle a zero use_count() gracefully.
  • Reassignment / reset: Appropriately decrementing existing counts and freeing existing resources before taking on new ones.

Hints

Hint 1

Maintain two raw pointer members: one to the managed object of type `T*` and one to a heap-allocated counter of type `size_t*`.

Hint 2

Use the copy-and-swap idiom for the copy assignment operator to ensure strong exception safety and automatically handle self-assignment.

Hint 3

Make sure that moving a `SharedPtr` nulls out the source pointer and reference counter so its destructor does not decrement the count.

Follow-up questions

  • How would you ensure thread safety for concurrent copies and destructions (e.g., using `std::atomic`)?
  • Why does `std::make_shared` perform a single heap allocation instead of two, and how would you redesign the control block to support that?
  • How does `std::weak_ptr` resolve cyclic reference issues, and what changes would be needed in the control block to support weak references?