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.
std::shared_ptr
to the newly created objectshared_ptr
construction<memory>
header#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;
}
std::make_shared<MyClass>(42)
creates a MyClass
object with the value 42.std::shared_ptr<MyClass>
managing the newly created object.#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;
}
std::make_shared
with direct construction.std::make_shared
is typically faster due to a single allocation for both the object and control block.#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;
}
std::make_shared
provides better exception safety.unsafeCreation
, if an exception is thrown after new
but before shared_ptr
construction, it can lead to a memory leak.std::make_shared
eliminates this risk by performing allocation and construction in a single step.Memory Efficiency: std::make_shared
allocates the object and control block in a single allocation, which can be more memory efficient.
Type Deduction: When used with auto
, std::make_shared
allows for easier type deduction.
Custom Deleters: std::make_shared
doesn't support custom deleters. Use the constructor directly if you need a custom deleter.
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.
Weak Pointers: The single allocation of std::make_shared
can potentially extend the lifetime of the control block when weak pointers are involved.
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.
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:
In both cases (unsafe and safe), the constructor of Risky
throws an exception when throwException
is true.
Both unsafeCreation
and safeCreation
catch this exception and print it.
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:
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.
[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