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_sharedperforms 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_ptrconstructor. - 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_sharedoffers 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_ptrconstructor is called, theTobject allocated bynewwill leak.make_sharedavoids 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_ptrandweak_ptrreferencing 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_ptrs still exist, leading to potential memory overhead until the lastweak_ptris 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"--> IComparison 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_sharedcannot be used directly. You'll need to use theshared_ptrconstructor that accepts a custom deleter. - Managing Arrays:
make_sharedis designed for single objects, not C-style arrays. Forshared_ptrs 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_ptrconstructor.make_sharedalways allocates a new object. - Memory Overhead with
weak_ptr: In rare cases where an object is very large and manyweak_ptrs outlive allshared_ptrs, 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.