Difference in make_shared and normal shared_ptr in C++
Categories:
make_shared vs. shared_ptr: Understanding the Nuances in C++

Explore the key differences between std::make_shared
and direct std::shared_ptr
construction in C++11 and beyond, focusing on performance, memory management, and exception safety.
In modern C++, std::shared_ptr
is a fundamental tool for managing dynamically allocated memory, preventing memory leaks, and handling object lifetimes in a multi-owner scenario. While you can construct a shared_ptr
directly, the C++11 standard introduced std::make_shared
as a preferred alternative. This article delves into the distinctions between these two methods, highlighting their implications for performance, memory layout, and exception safety.
The Basics: shared_ptr Construction
Before diving into make_shared
, let's briefly review how std::shared_ptr
can be constructed directly. A shared_ptr
manages a raw pointer and a control block. The control block contains essential information like the reference count and a weak count. When you construct a shared_ptr
from a raw pointer, these two components (the object itself and its control block) are typically allocated separately.
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed!\n"; }
~MyClass() { std::cout << "MyClass destructed!\n"; }
void greet() { std::cout << "Hello from MyClass!\n"; }
};
int main() {
// Direct shared_ptr construction
std::shared_ptr<MyClass> ptr1(new MyClass());
ptr1->greet();
// ... other operations
return 0;
}
Direct std::shared_ptr
construction from a raw pointer.
Introducing make_shared
std::make_shared
is a factory function that creates and returns a std::shared_ptr
. Its primary advantage lies in its ability to perform a single memory allocation for both the managed object and its control block. This co-location of data can lead to significant performance benefits and improved memory locality.
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed!\n"; }
~MyClass() { std::cout << "MyClass destructed!\n"; }
void greet() { std::cout << "Hello from MyClass!\n"; }
};
int main() {
// Using std::make_shared
std::shared_ptr<MyClass> ptr2 = std::make_shared<MyClass>();
ptr2->greet();
// ... other operations
return 0;
}
Using std::make_shared
for std::shared_ptr
creation.
Key Differences and Advantages of make_shared
The choice between make_shared
and direct shared_ptr
construction boils down to several critical factors:
- Single Allocation:
make_shared
performs a single memory allocation for both the object and its control block. Direct construction, on the other hand, typically involves two separate allocations: one for the object (new MyClass()
) and another for the control block by theshared_ptr
constructor. - Performance: A single allocation can be faster due to reduced overhead from the memory allocator. Furthermore, co-locating the object and control block can improve cache locality, leading to better runtime performance.
- Exception Safety:
make_shared
offers better exception safety in certain scenarios. Consider a function call likef(shared_ptr<T>(new T()), g())
. Ifnew T()
succeeds, butg()
throws an exception before theshared_ptr
constructor is called, theT
object allocated bynew
will leak.make_shared
avoids this by ensuring the object and control block are created atomically. - Memory Deallocation: With
make_shared
, the memory for the object and control block is deallocated together when the lastshared_ptr
andweak_ptr
referencing the control block are destroyed. In direct construction, the object's memory is freed when its reference count drops to zero, but the control block might persist longer ifweak_ptr
s still exist, leading to potential memory overhead until the lastweak_ptr
is destroyed.
flowchart TD subgraph make_shared A[Single Allocation] --> B{Object + Control Block} end subgraph Direct shared_ptr C[Allocation 1] --> D{Object} E[Allocation 2] --> F{Control Block} end B --"Better Cache Locality"--> G[Performance Benefit] B --"Atomic Creation"--> H[Exception Safety] D --"Separate Lifetime"--> I[Control Block Persists Longer (with weak_ptr)] F --"Separate Lifetime"--> I
Comparison of memory allocation and benefits between make_shared
and direct shared_ptr
.
std::make_shared
over direct std::shared_ptr
construction unless you have a specific reason not to (e.g., custom deleters, managing an array, or constructing from an already existing raw pointer).When Not to Use make_shared
While make_shared
is generally preferred, there are a few scenarios where it might not be suitable:
- Custom Deleters: If you need to provide a custom deleter for your object,
make_shared
cannot be used directly. You'll need to use theshared_ptr
constructor that accepts a custom deleter. - Managing Arrays:
make_shared
is designed for single objects, not C-style arrays. Forshared_ptr
s managing arrays, you'd typically usestd::shared_ptr<T[]>(new T[N])
with a custom deleter orstd::unique_ptr
. - Constructing from an Existing Raw Pointer: If you already have a raw pointer to an object that needs to be managed by a
shared_ptr
, you must use theshared_ptr
constructor.make_shared
always allocates a new object. - Memory Overhead with
weak_ptr
: In rare cases where an object is very large and manyweak_ptr
s outlive allshared_ptr
s, the memory for the object (even if destructed) might be held longer by the control block allocated bymake_shared
. This is because the control block and object are in a single allocation. If the object and control block were separate, the object's memory could be reclaimed sooner.
#include <memory>
#include <iostream>
void customDeleter(int* p) {
std::cout << "Custom deleter called!\n";
delete p;
}
int main() {
// Using custom deleter (cannot use make_shared)
std::shared_ptr<int> ptr_with_deleter(new int(10), customDeleter);
std::cout << *ptr_with_deleter << "\n";
// Managing an existing raw pointer (cannot use make_shared)
int* raw_ptr = new int(20);
std::shared_ptr<int> ptr_from_raw(raw_ptr);
std::cout << *ptr_from_raw << "\n";
// Example of make_shared for comparison
std::shared_ptr<int> ptr_make_shared = std::make_shared<int>(30);
std::cout << *ptr_make_shared << "\n";
return 0;
}
Scenarios where make_shared
is not applicable.