std::make_shared


Function: std::make_shared in C++

std::make_shared is a function template in C++ that creates and returns a std::shared_ptr to a newly allocated object. It's part of the C++ Standard Library and provides a more efficient and safer way to create shared pointers compared to using the std::shared_ptr constructor directly.

Key Characteristics

Example 1: Basic Usage

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int value) : value_(value) {
        std::cout << "MyClass constructed with value: " << value_ << std::endl;
    }
    ~MyClass() {
        std::cout << "MyClass destructed with value: " << value_ << std::endl;
    }
    int getValue() const { return value_; }
private:
    int value_;
};

int main() {
    auto ptr = std::make_shared<MyClass>(42);
    std::cout << "Value: " << ptr->getValue() << std::endl;
    return 0;
}

Explanation:

Example 2: Comparison with Direct Construction

#include <iostream>
#include <memory>
#include <chrono>

class LargeObject {
public:
    LargeObject() { std::cout << "LargeObject constructed" << std::endl; }
    ~LargeObject() { std::cout << "LargeObject destructed" << std::endl; }
    int data[1000000];
};

int main() {
    {
        auto start = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < 100000; ++i) {
            std::shared_ptr<LargeObject> ptr(new LargeObject());
        }
        auto end = std::chrono::high_resolution_clock::now();
        std::cout << "Direct construction time: " 
                  << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() 
                  << " ms" << std::endl;
    }

    {
        auto start = std::chrono::high_resolution_clock::now();
        for (int i = 0; i < 100000; ++i) {
            auto ptr = std::make_shared<LargeObject>();
        }
        auto end = std::chrono::high_resolution_clock::now();
        std::cout << "make_shared time: " 
                  << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() 
                  << " ms" << std::endl;
    }

    return 0;
}

Explanation:

Example 3: Exception Safety

#include <iostream>
#include <memory>
#include <stdexcept>

class Risky {
public:
    Risky(bool throwException) {
        if (throwException) {
            throw std::runtime_error("Construction failed");
        }
        std::cout << "Risky object constructed" << std::endl;
    }
    ~Risky() {
        std::cout << "Risky object destructed" << std::endl;
    }
};

void unsafeCreation(bool throwException) {
    try {
        std::shared_ptr<Risky> ptr(new Risky(throwException));
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
}

void safeCreation(bool throwException) {
    try {
        auto ptr = std::make_shared<Risky>(throwException);
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
}

int main() {
    std::cout << "Unsafe creation:" << std::endl;
    unsafeCreation(true);

    std::cout << "\nSafe creation:" << std::endl;
    safeCreation(true);

    return 0;
}

Explanation:

Additional Considerations

  1. Memory Efficiency: std::make_shared allocates the object and control block in a single allocation, which can be more memory efficient.

  2. Type Deduction: When used with auto, std::make_shared allows for easier type deduction.

  3. Custom Deleters: std::make_shared doesn't support custom deleters. Use the constructor directly if you need a custom deleter.

  4. Array Support: Prior to C++20, std::make_shared doesn't support array types. Use std::make_shared<T[]> in C++20 and later for arrays.

  5. Weak Pointers: The single allocation of std::make_shared can potentially extend the lifetime of the control block when weak pointers are involved.

Summary

std::make_shared is a powerful and efficient way to create shared pointers in C++. It offers several advantages over direct construction, including improved performance through single allocation, better exception safety, and more concise syntax when used with auto. While it's not suitable for all scenarios (such as when custom deleters are needed), it should be the preferred method for creating shared pointers in most cases. Understanding and using std::make_shared can lead to more efficient and safer code, particularly in scenarios involving frequent allocations or where exception safety is critical.

Addendum: Explanation of the output of example3

You're absolutely right, and I apologize for not providing the expected output in my original response. The output you're seeing is correct. Let me explain why this is the case and what it means:

Unsafe creation:
Caught exception: Construction failed

Safe creation:
Caught exception: Construction failed

This output is correct and demonstrates an important point about std::make_shared, but it doesn't show the full benefit of using make_shared in terms of exception safety. Let me explain:

  1. In both cases (unsafe and safe), the constructor of Risky throws an exception when throwException is true.

  2. Both unsafeCreation and safeCreation catch this exception and print it.

  3. The key difference between std::make_shared and the direct construction isn't visible in this output because the exception is thrown during object construction, not between allocation and shared_ptr construction.

The real benefit of std::make_shared in terms of exception safety comes into play in scenarios where:

  1. The object construction succeeds, but
  2. An exception is thrown after the object is created but before the shared_ptr takes ownership.

This scenario isn't demonstrated in the given example. To illustrate this, we'd need a more complex example where there's a possibility of an exception between the new expression and the shared_ptr constructor.

Here's a modified example that better demonstrates the exception safety advantage of std::make_shared:

#include <iostream>
#include <memory>
#include <stdexcept>

class Risky {
public:
    Risky() { std::cout << "Risky object constructed" << std::endl; }
    ~Risky() { std::cout << "Risky object destructed" << std::endl; }
};

void unsafeCreation() {
    try {
        Risky* ptr = new Risky();
        if (true) { // Simulate an exception between new and shared_ptr construction
            throw std::runtime_error("Exception after new");
        }
        std::shared_ptr<Risky> sharedPtr(ptr); // Never reached
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
        // Memory leak! ptr is not deleted
    }
}

void safeCreation() {
    try {
        auto sharedPtr = std::make_shared<Risky>();
        throw std::runtime_error("Exception after make_shared");
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
        // No memory leak, Risky object is properly destroyed
    }
}

int main() {
    std::cout << "Unsafe creation:" << std::endl;
    unsafeCreation();

    std::cout << "\nSafe creation:" << std::endl;
    safeCreation();

    return 0;
}

The expected output for this modified example would be:

Unsafe creation:
Risky object constructed
Caught exception: Exception after new

Safe creation:
Risky object constructed
Caught exception: Exception after make_shared
Risky object destructed

This output demonstrates that in the unsafe case, the Risky object is constructed but never destructed (causing a memory leak), while in the safe case using std::make_shared, the object is properly destructed even when an exception is thrown after its creation.

Citations:

[1] https://cplusplus.com/reference/memory/make_shared/ [2] https://www.reddit.com/r/cpp_questions/comments/q3lhcu/stdmake_sharedt_advantages/ [3] https://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared [4] https://stackoverflow.com/questions/20895648/difference-in-make-shared-and-normal-shared-ptr-in-c [5] https://www.javatpoint.com/make_shared-in-cpp

Previous Page | Course Schedule | Course Content