C++ malloc vs new vs make_unique
이 글의 핵심
C++ malloc vs new vs make_unique: Complete memory allocation comparison. Differences in constructor calling, exception safety, RAII.
Introduction
In C++, memory allocation has three methods: malloc, new, make_unique. Each differs in constructor calling, type safety, automatic deallocation, etc. To use an analogy, malloc/free is like just renting land, new/delete is building and demolishing, make_unique is leaving to management company for automatic demolition at contract end. In modern C++, it’s best to choose RAII path when possible.
After Reading This
- Understand differences between malloc vs new vs make_unique
- Grasp differences in constructor calling, type safety, exception handling
- Learn performance comparison and practical selection criteria
- Check importance of RAII and exception safety
Table of Contents
- malloc vs new vs make_unique Differences
- Practical Implementation
- Advanced Usage
- Performance Comparison
- Practical Cases
- Troubleshooting
- Conclusion
malloc vs new vs make_unique Differences
Comparison Table
| Item | malloc | new | make_unique |
|---|---|---|---|
| Constructor call | ❌ | ✅ | ✅ |
| Type safety | ❌ (casting needed) | ✅ | ✅ |
| Exception handling | Returns nullptr | Throws bad_alloc | Throws bad_alloc |
| Deallocation method | free | delete | Automatic |
| Array allocation | malloc(n * sizeof(T)) | new T[n] | make_unique<T[]>(n) |
| RAII | ❌ | ❌ | ✅ |
| Exception safety | ❌ | ❌ | ✅ |
| C++11 onwards | C compatible | Legacy | ✅ Recommended |
Practical Implementation
1) malloc: C Style
#include <cstdlib>
#include <iostream>
int main() {
// malloc: constructor not called
int* p = (int*)malloc(sizeof(int));
if (p == nullptr) {
std::cerr << "Allocation failed" << std::endl;
return 1;
}
*p = 42;
std::cout << *p << std::endl;
free(p);
return 0;
}
2) new: C++ Style
#include <iostream>
class MyClass {
private:
int x_;
public:
MyClass(int x) : x_(x) {
std::cout << "Constructor: " << x_ << std::endl;
}
~MyClass() {
std::cout << "Destructor: " << x_ << std::endl;
}
int getValue() const { return x_; }
};
int main() {
// new: constructor called
MyClass* p = new MyClass(42);
std::cout << p->getValue() << std::endl;
delete p; // destructor called
return 0;
}
Output:
Constructor: 42
42
Destructor: 42
3) make_unique: Modern C++ (C++14)
#include <iostream>
#include <memory>
class MyClass {
private:
int x_;
public:
MyClass(int x) : x_(x) {
std::cout << "Constructor: " << x_ << std::endl;
}
~MyClass() {
std::cout << "Destructor: " << x_ << std::endl;
}
int getValue() const { return x_; }
};
int main() {
// make_unique: constructor call + automatic deallocation
{
auto p = std::make_unique<MyClass>(42);
std::cout << p->getValue() << std::endl;
} // Destructor automatically called
std::cout << "After block end" << std::endl;
return 0;
}
Output:
Constructor: 42
42
Destructor: 42
After block end
4) Array Allocation
#include <iostream>
#include <memory>
int main() {
// malloc: array
int* arr1 = (int*)malloc(10 * sizeof(int));
for (int i = 0; i < 10; ++i) {
arr1[i] = i;
}
free(arr1);
// new: array
int* arr2 = new int[10];
for (int i = 0; i < 10; ++i) {
arr2[i] = i;
}
delete[] arr2; // delete[]
// make_unique: array
auto arr3 = std::make_unique<int[]>(10);
for (int i = 0; i < 10; ++i) {
arr3[i] = i;
}
// Automatic deallocation
return 0;
}
5) Exception Safety
#include <iostream>
#include <memory>
void process(int* data, int size) {
if (size <= 0) {
throw std::invalid_argument("size must be positive");
}
for (int i = 0; i < size; ++i) {
data[i] = i;
}
}
int main() {
// ❌ new: memory leak on exception
int* p1 = new int[10];
try {
process(p1, -1); // Exception thrown
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
delete[] p1; // Manual deallocation needed
}
// ✅ make_unique: automatic deallocation on exception
try {
auto p2 = std::make_unique<int[]>(10);
process(p2.get(), -1); // Exception thrown
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
// Automatic deallocation
}
return 0;
}
Advanced Usage
1) Custom Deleters with unique_ptr
make_unique covers the common case, but when a resource needs non-default cleanup (a C API handle, a pooled allocation), pair unique_ptr with a custom deleter instead of falling back to raw new/delete.
#include <memory>
#include <cstdio>
struct FileCloser {
void operator()(FILE* fp) const { if (fp) std::fclose(fp); }
};
int main() {
std::unique_ptr<FILE, FileCloser> file(std::fopen("data.txt", "r"));
if (file) {
// use file.get()
} // FileCloser::operator() runs automatically
return 0;
}
2) Placement new for Custom Allocators
When you need to construct an object in pre-allocated memory (arenas, memory pools, shared-memory buffers), plain make_unique cannot help — it always allocates. Placement new separates allocation from construction.
#include <new>
alignas(MyClass) char buffer[sizeof(MyClass)];
MyClass* obj = new (buffer) MyClass(42); // construct in existing memory
obj->~MyClass(); // must call destructor manually — no delete
3) allocate_shared vs make_shared
make_shared bundles the control block and the object in one allocation, but always uses operator new. std::allocate_shared gives the same single-allocation benefit while routing through a custom allocator (e.g. a pool allocator for a hot path).
#include <memory>
#include <memory_resource>
std::pmr::synchronized_pool_resource pool;
auto sp = std::allocate_shared<MyClass>(
std::pmr::polymorphic_allocator<MyClass>(&pool), 42);
Performance Comparison
Benchmark Results
// Benchmark: 1 million allocations
// malloc: 120ms
// new: 125ms
// make_unique: 130ms
// Difference: ~8% (negligible)
Conclusion: Performance difference is minimal. Safety and convenience are more important.
Practical Cases
1) Interop with a C Library That Owns malloc’d Memory
When a C library allocates a buffer internally and hands it back via a function pointer that expects free, you must match malloc/free — wrapping it in unique_ptr with a free-based deleter keeps it exception-safe without breaking the C API contract.
#include <memory>
#include <cstdlib>
extern "C" char* c_library_alloc_string(); // returns malloc'd buffer
struct FreeDeleter {
void operator()(char* p) const { std::free(p); }
};
void useLibrary() {
std::unique_ptr<char, FreeDeleter> str(c_library_alloc_string());
// automatically freed with free(), matching the library's contract
}
2) Factory Function Returning unique_ptr
Constructing via make_unique inside a factory keeps ownership explicit at the call site and makes leaks on early-return paths impossible.
#include <memory>
class Widget { public: explicit Widget(int id) : id_(id) {} private: int id_; };
std::unique_ptr<Widget> createWidget(int id) {
if (id < 0) return nullptr;
return std::make_unique<Widget>(id);
}
3) Bulk Allocation Hot Path Falling Back to malloc
In a rare profiled hot path (e.g. a custom arena allocator’s backing store) where the constructor call and exception machinery of new are unnecessary overhead for a POD buffer, raw malloc is still a defensible, explicit choice — as long as it stays contained behind a small wrapper.
struct Arena {
char* buffer;
explicit Arena(size_t bytes) : buffer(static_cast<char*>(std::malloc(bytes))) {}
~Arena() { std::free(buffer); }
};
Troubleshooting
1) Mixing Allocation/Deallocation Pairs
The single most common bug in this area: allocating with one method and freeing with another. Each pair (malloc/free, new/delete, new[]/delete[]) must match exactly, or the behavior is undefined.
int* p = new int(5);
free(p); // ❌ undefined behavior: new'd memory freed with free()
int* arr = new int[10];
delete arr; // ❌ undefined behavior: missing [] — only first element's destructor runs
2) Forgetting nullptr Check on malloc
Unlike new, malloc does not throw on failure — it returns nullptr. Skipping the check turns an allocation failure into a null-pointer dereference later, often far from the actual cause.
int* p = (int*)malloc(huge_size);
*p = 42; // ❌ crashes with no diagnostic if malloc returned nullptr
// ✅ always check:
if (!p) { /* handle allocation failure */ }
3) make_unique<T[]> Value-Initializes, new T[n] Does Not (for Non-Class Types)
std::make_unique<int[]>(n) value-initializes every element to zero, while new int[n] (without ()) leaves the elements uninitialized. Assuming they behave identically can leave garbage values in what looks like a “safe, modern” allocation.
auto a = std::make_unique<int[]>(10); // all zero-initialized
int* b = new int[10]; // uninitialized — garbage values
int* c = new int[10](); // zero-initialized, matches make_unique
4) Leaking Through a Raw Pointer Returned from .release()
Calling .release() on a unique_ptr hands back a raw pointer and disowns it — if the caller then forgets to manage it (or an exception is thrown before it is stored), the RAII guarantee is gone.
std::unique_ptr<Widget> makeWidget();
Widget* w = makeWidget().release(); // ⚠️ now a plain raw pointer — no automatic cleanup
// prefer keeping it inside a smart pointer unless a raw-pointer API truly requires it
Summary
Key Points
- malloc: C function, no constructor, manual management
- new: C++ operator, calls constructor, manual deallocation
- make_unique: Modern C++, RAII, automatic deallocation
- Recommendation: Use make_unique in modern C++
- Exception safety: make_unique is safest
Decision Flowchart
C++ object?
├─ Yes
│ └─ Modern C++ (C++14+)?
│ ├─ Yes → make_unique (recommended)
│ └─ No → new/delete
└─ No (POD type)
└─ C library integration?
├─ Yes → malloc/free
└─ No → make_unique (safer)
Best Practices
- ✅ Use make_unique in modern C++
- ✅ Use RAII for automatic resource management
- ✅ Avoid raw pointers
- ❌ Don’t mix malloc/new/make_unique
- ❌ Don’t use new in modern C++
Related Articles
- C++ new vs malloc
- C++ shared_ptr vs unique_ptr
- C++ Memory Management Deep Dive Master modern C++ memory management! 🚀
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. C++ malloc vs new vs make_unique: Complete memory allocation comparison.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ new vs malloc | 생성자·타입 안전성·예외 처리 완벽 비교
- C++ shared_ptr vs unique_ptr | ‘어떤 스마트 포인터?’ 선택 가이드
- C++ 메모리 관리 | ‘new/delete/RAII’ 완벽 정리
Keywords Covered in This Article (Related Search Terms)
This article covers C++, malloc, new, make_unique, memory-allocation, RAII, smart-pointer.