Interview

10 Smart Pointer Interview Questions and Answers

Prepare for your C++ interview with this guide on smart pointers, covering efficient memory management and common interview questions.

Smart pointers are a crucial feature in modern C++ programming, designed to manage dynamic memory more efficiently and safely. They help prevent common issues such as memory leaks and dangling pointers by automatically handling the allocation and deallocation of memory. Smart pointers encapsulate raw pointers, providing a more robust and error-resistant way to manage resources in complex applications.

This article offers a curated selection of smart pointer interview questions, complete with detailed answers. By familiarizing yourself with these questions, you will gain a deeper understanding of smart pointers and be better prepared to demonstrate your expertise in technical interviews.

Smart Pointer Interview Questions and Answers

1. Explain the primary purpose of using smart pointers in C++.

Smart pointers in C++ manage dynamically allocated memory automatically and safely, preventing memory leaks and dangling pointers. The primary types are std::unique_ptr, std::shared_ptr, and std::weak_ptr.

  • std::unique_ptr: Exclusively owns the object it points to. The object is deleted when the std::unique_ptr goes out of scope.
  • std::shared_ptr: Allows multiple pointers to share ownership of the same object, which is deleted when the last std::shared_ptr is destroyed.
  • std::weak_ptr: Used with std::shared_ptr to break circular references. It does not own the object and does not affect its lifetime.

Example:

#include <iostream>
#include <memory>

void uniquePtrExample() {
    std::unique_ptr<int> uniquePtr = std::make_unique<int>(10);
    std::cout << "Unique Pointer Value: " << *uniquePtr << std::endl;
}

void sharedPtrExample() {
    std::shared_ptr<int> sharedPtr1 = std::make_shared<int>(20);
    std::shared_ptr<int> sharedPtr2 = sharedPtr1;
    std::cout << "Shared Pointer Value: " << *sharedPtr1 << std::endl;
    std::cout << "Shared Pointer Use Count: " << sharedPtr1.use_count() << std::endl;
}

int main() {
    uniquePtrExample();
    sharedPtrExample();
    return 0;
}

2. Write a simple code snippet to demonstrate how to create and use a unique_ptr.

A unique_ptr ensures exclusive ownership of a dynamically allocated object, automatically deleting it when the unique_ptr goes out of scope.

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass() { std::cout << "MyClass Constructor\n"; }
    ~MyClass() { std::cout << "MyClass Destructor\n"; }
    void display() { std::cout << "Hello from MyClass\n"; }
};

int main() {
    std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
    ptr->display();
    return 0;
}

3. What is a weak_ptr, and why would you use it instead of a shared_ptr?

A weak_ptr holds a non-owning reference to an object managed by shared_ptr, preventing circular references that can lead to memory leaks.

#include <iostream>
#include <memory>

class Node {
public:
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev; // Prevents circular reference

    ~Node() {
        std::cout << "Node destroyed" << std::endl;
    }
};

int main() {
    auto node1 = std::make_shared<Node>();
    auto node2 = std::make_shared<Node>();

    node1->next = node2;
    node2->prev = node1; // No circular reference

    return 0;
}

4. Write a code example showing how to avoid a circular reference using weak_ptr.

Circular references occur when objects reference each other, creating a cycle that prevents the reference count from reaching zero. weak_ptr breaks these cycles by holding a non-owning reference.

#include <iostream>
#include <memory>

class B; // Forward declaration

class A {
public:
    std::shared_ptr<B> ptrB;
    ~A() { std::cout << "A destroyed\n"; }
};

class B {
public:
    std::weak_ptr<A> ptrA; // Avoids circular reference
    ~B() { std::cout << "B destroyed\n"; }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->ptrB = b;
    b->ptrA = a;

    return 0;
}

5. Explain how custom deleters work with unique_ptr and provide a code example.

Custom deleters in unique_ptr allow specifying a custom function to release resources when the unique_ptr goes out of scope.

#include <iostream>
#include <memory>

struct FileDeleter {
    void operator()(FILE* file) const {
        if (file) {
            std::cout << "Closing file\n";
            fclose(file);
        }
    }
};

int main() {
    std::unique_ptr<FILE, FileDeleter> filePtr(fopen("example.txt", "w"));
    if (filePtr) {
        std::cout << "File opened successfully\n";
    }
    return 0;
}

6. Write a code snippet demonstrating exception safety with smart pointers.

#include <iostream>
#include <memory>

void process_data(std::shared_ptr<int> ptr) {
    if (!ptr) {
        throw std::runtime_error("Null pointer exception");
    }
    std::cout << "Processing data: " << *ptr << std::endl;
}

int main() {
    try {
        std::shared_ptr<int> data = std::make_shared<int>(42);
        process_data(data);
    } catch (const std::exception& e) {
        std::cerr << "Exception caught: " << e.what() << std::endl;
    }
    return 0;
}

7. Explain the purpose and usage of make_unique and make_shared.

make_unique and make_shared simplify the creation of std::unique_ptr and std::shared_ptr, respectively, ensuring resources are properly released.

#include <memory>
#include <iostream>

int main() {
    std::unique_ptr<int> uniquePtr = std::make_unique<int>(42);
    std::cout << "Unique Pointer Value: " << *uniquePtr << std::endl;

    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
    std::cout << "Shared Pointer Value: " << *sharedPtr << std::endl;

    return 0;
}

8. Discuss the ownership semantics of unique_ptr, shared_ptr, and weak_ptr.

Smart pointers manage the lifetime of dynamically allocated objects with different ownership semantics.

  • unique_ptr: Exclusive ownership. The object is deleted when the unique_ptr goes out of scope. It can be moved to transfer ownership.
#include <memory>
#include <iostream>

int main() {
    std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
    std::unique_ptr<int> ptr2 = std::move(ptr1); // Transfer ownership
    std::cout << *ptr2 << std::endl; // Output: 10
    return 0;
}
  • shared_ptr: Shared ownership. The object is deleted when the last shared_ptr is destroyed. Uses reference counting.
#include <memory>
#include <iostream>

int main() {
    std::shared_ptr<int> ptr1 = std::make_shared<int>(20);
    std::shared_ptr<int> ptr2 = ptr1; // Shared ownership
    std::cout << *ptr1 << std::endl; // Output: 20
    std::cout << *ptr2 << std::endl; // Output: 20
    return 0;
}
  • weak_ptr: Non-owning reference. Used to break circular references. Can be converted to shared_ptr if needed.
#include <memory>
#include <iostream>

int main() {
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(30);
    std::weak_ptr<int> weakPtr = sharedPtr; // Weak reference
    if (auto tempPtr = weakPtr.lock()) { // Convert to shared_ptr
        std::cout << *tempPtr << std::endl; // Output: 30
    } else {
        std::cout << "Object no longer exists" << std::endl;
    }
    return 0;
}

9. Explain how to convert between different types of smart pointers.

To convert between smart pointers:

  • Convert std::unique_ptr to std::shared_ptr using std::move.
  • Convert std::shared_ptr to std::weak_ptr directly.
  • Convert std::weak_ptr back to std::shared_ptr using the lock method.

Example:

#include <iostream>
#include <memory>

int main() {
    std::unique_ptr<int> uniquePtr = std::make_unique<int>(10);
    std::shared_ptr<int> sharedPtr = std::move(uniquePtr);

    std::weak_ptr<int> weakPtr = sharedPtr;

    if (auto lockedPtr = weakPtr.lock()) {
        std::cout << "Value: " << *lockedPtr << std::endl;
    } else {
        std::cout << "Pointer is expired." << std::endl;
    }

    return 0;
}

10. Given a scenario where multiple threads need to access a shared resource, how would you use smart pointers to manage this safely?

To manage shared resources safely in a multi-threaded environment, use smart pointers with synchronization primitives like std::mutex. Smart pointers manage the lifetime of objects, while a mutex ensures thread safety.

#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>

std::shared_ptr<int> sharedResource;
std::mutex resourceMutex;

void accessResource(int id) {
    std::lock_guard<std::mutex> lock(resourceMutex);
    if (sharedResource) {
        std::cout << "Thread " << id << " accessing resource: " << *sharedResource << std::endl;
    } else {
        std::cout << "Thread " << id << " found resource uninitialized." << std::endl;
    }
}

int main() {
    sharedResource = std::make_shared<int>(42);

    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(accessResource, i);
    }

    for (auto& thread : threads) {
        thread.join();
    }

    return 0;
}

In this example, std::shared_ptr manages the shared resource, and std::mutex ensures only one thread accesses the resource at a time. The std::lock_guard provides a convenient mechanism for acquiring and releasing the mutex.

Previous

15 Linear Algebra Interview Questions and Answers

Back to Interview
Next

10 Memory Management Interview Questions and Answers