new and delete
Written and reviewed by Gagan Bhardwaj · Senior IT Faculty · 15+ years’ experience
Storage, Object Lifetime and Ownership
Dynamic allocation has three distinct ideas that beginners often mix:
- Storage: bytes must be obtained with suitable size and alignment.
- Object lifetime: an object is initialized/constructed in that storage and later destroyed.
- Ownership: some program component is responsible for ending the lifetime and releasing the resource exactly once.
A C++ new-expression normally combines allocation and initialization, then yields a pointer. A delete-expression destroys the pointed-to object and invokes the corresponding deallocation function.
int local = 42; // automatic lifetime: preferred when possible
int* dynamic = new int{42}; // dynamic lifetime: manual ownership
delete dynamic;
dynamic = nullptr;Single-Object new and delete
#include <iostream>
int main() {
int* score = new int{95};
std::cout << "Score: " << *score << '\n';
delete score;
score = nullptr;
}new int{95} allocates and initializes one integer. The returned pointer is dereferenced with *score. Exactly one matching delete score is required along every successful ownership path.
After deletion, the old pointer value is dangling and must not be read or dereferenced. Assigning nullptr helps this variable, but any copied pointer still dangles. That is why “remember to delete” is not a scalable ownership strategy.
| Expression | Meaning |
|---|---|
new int | default-initialized int; value is indeterminate |
new int() | value-initialized to zero |
new int{} | value-initialized to zero |
new int{95} | initialized to 95 |
Brace initialization makes the intended initial value explicit and rejects many narrowing conversions.
Dynamic Arrays and Matching Forms
int* marks = new int[3]{72, 88, 91};
for (int i = 0; i < 3; ++i) {
std::cout << marks[i] << ' ';
}
delete[] marks;
marks = nullptr;The syntax pair is strict:
| Allocation | Required release |
|---|---|
new T | delete pointer |
new T[n] | delete[] pointer |
Mixing the forms, deleting the same allocation twice, deleting an interior pointer, or deleting storage not obtained by a matching new-expression causes undefined behavior.
Prefer a container:
std::vector<int> marks{72, 88, 91};
marks.push_back(96); // owns memory, tracks size, releases automaticallyVector supplies size, bounds-aware at(), iteration and exception-safe cleanup. Use std::string for text rather than manually managed character arrays. Continue with vector and dynamic-memory fundamentals.
Object Construction and Destruction
#include <iostream>
#include <string>
#include <utility>
class Student {
public:
explicit Student(std::string name) : name_(std::move(name)) {
std::cout << "Constructed: " << name_ << '\n';
}
~Student() {
std::cout << "Destroyed: " << name_ << '\n';
}
void print() const { std::cout << "Student: " << name_ << '\n'; }
private:
std::string name_;
};
int main() {
Student* student = new Student{"Aman"};
student->print();
delete student;
}Construction completes before new returns. delete first invokes the destructor and then deallocates storage. For arrays of class objects, delete[] destroys each constructed element.
If a constructor throws, the new-expression releases the storage it obtained through the matching deallocation mechanism; no pointer is returned. Resources acquired inside class members should themselves be RAII owners so partial construction is safely unwound. See destructors.
Allocation Failure and Advanced Forms
Throwing new
#include <cstddef>
#include <new>
constexpr std::size_t huge_count = 1'000'000;
try {
auto* data = new int[huge_count];
delete[] data;
} catch (const std::bad_alloc&) {
// recover, report, or propagate at an appropriate boundary
}Ordinary allocation failure normally throws std::bad_alloc. Catch it only where the program has a meaningful recovery policy; blindly retrying can worsen memory pressure.
Nothrow new
int* value = new (std::nothrow) int{7};
if (value == nullptr) {
// handle failure
} else {
delete value;
}The nothrow form returns null on allocation failure. It does not remove other exceptions that an object's constructor may throw.
Placement new: advanced infrastructure tool
alignas(Student) std::byte storage[sizeof(Student)];
Student* p = new (storage) Student{"Aman"};
p->~Student(); // storage itself was not allocated by ordinary newPlacement new constructs an object in supplied storage. Do not apply ordinary delete to that pointer. Correct alignment, lifetime reuse, exceptions and explicit destruction make this a low-level technique for allocators/containers, not ordinary application code.
Ownership Rules and Failure Modes
| Bug | What happens | Design prevention |
|---|---|---|
| Memory leak | Owner loses the only pointer | RAII owner |
| Double delete | Same allocation released twice | Unique ownership type |
| Use after free | Dangling pointer is accessed | Clear lifetime boundaries |
| Mismatched delete | new[]/delete mismatch | Container/RAII |
| Ownership ambiguity | No one knows who releases | Express owner in the type/API |
| Exception leak | Control exits before delete | Acquire directly into RAII |
void process(const Student* student); // non-owning observation
void take(std::unique_ptr<Student> student); // ownership transfer
Student& require_student(Student& student); // non-null borrowed objectA raw pointer or reference should normally be a non-owning view. If an API transfers ownership, express that in a smart-pointer type and document nullability/lifetime. Never create two independent owning smart pointers from the same raw pointer.
RAII, unique_ptr and shared_ptr
Resource Acquisition Is Initialization (RAII) ties resource ownership to an object's lifetime. Cleanup happens in the destructor on normal return, early return and exception unwinding.
#include <memory>
int main() {
auto student = std::make_unique<Student>("Aman");
student->print();
} // Student is destroyed automatically| Need | Preferred type |
|---|---|
| Scoped ordinary value | Object by value |
| Dynamic sequence | vector/string |
| Exclusive dynamic owner | unique_ptr + make_unique |
| Genuinely shared lifetime | shared_ptr + make_shared |
| Non-owning observation of shared object | weak_ptr where needed |
Prefer unique_ptr: its ownership is explicit, movable and inexpensive. Use shared_ptr only when the ownership graph genuinely requires reference-counted shared lifetime; cycles need weak_ptr or a redesigned owner relationship.
Do not pass shared_ptr everywhere by habit. Pass T&/const T& when a function only uses an existing object, and pass/return an owning type only when ownership semantics require it. Study smart pointers.
Common Mistakes and 100-Point Safety Checklist
| Mistake | Correction |
|---|---|
| Manual array for ordinary collection | Use vector/array/string |
| Return before delete | Acquire immediately into RAII |
delete after new[] | Match forms; preferably remove manual pair |
| Dereference without failure/lifetime proof | Validate and express invariants |
| Two owners for one raw pointer | Create one owner and borrow from it |
shared_ptr by default | Prefer value/unique ownership |
| Placement new in business code | Keep it inside expert infrastructure |
- Can the object have automatic storage?
- Can a standard container own the data?
- Is the owner visible in the type?
- Is exclusive ownership sufficient?
- Are allocation/deallocation forms matched?
- Are exceptions and early returns safe?
- Are all borrows shorter than the owner lifetime?
- Are array bounds and sizes explicit?
- Are AddressSanitizer/UndefinedBehaviorSanitizer tests run?
- Does review find zero naked owning
new/deleteoutside RAII internals?
g++ -std=c++20 -Wall -Wextra -Wpedantic \
-fsanitize=address,undefined -fno-omit-frame-pointer app.cppAuthoritative References
- C++ Working Draft: New Expressions
- C++ Working Draft: Delete Expressions
- C++ Core Guidelines: Avoid explicit new and delete
The object-lifetime, matching-form and allocation-failure rules were checked against the current working draft. The production guidance follows the C++ Core Guidelines: prefer scoped objects, RAII containers and ownership types over naked owning new/delete.
Frequently Asked Questions
What exactly do new and delete do in C++?
What happens if new[] is paired with delete instead of delete[]?
Is deleting a null pointer safe?
What happens when memory allocation fails?
When should modern C++ code use raw new and delete?
Storage, Lifetime और Ownership
- Storage: suitable size/alignment के bytes मिलें।
- Lifetime: object construct होकर बाद में destroy हो।
- Ownership: कोई component exactly once cleanup की जिम्मेदारी ले।
int local=42; // automatic, preferred
int* dynamic=new int{42}; // manual dynamic owner
delete dynamic;
dynamic=nullptr;new allocation + initialization करता, delete destructor + deallocation। Pointer चाहिए इसलिए dynamic allocation जरूरी नहीं; pointer non-owning भी हो सकता है।
Single Object new और delete
int* score=new int{95};
std::cout<<"Score: "<<*score<<'\n';
delete score;
score=nullptr;Successful allocation path पर exactly one matching delete चाहिए। Delete के बाद pointer dangling; nullptr assignment केवल उसी variable को मदद करता है, copied aliases फिर भी dangling हैं। new int का value indeterminate, new int{} zero और new int{95} 95 initialize करता है।
Dynamic Arrays और Matching Forms
int* marks=new int[3]{72,88,91};
for(int i=0;i<3;++i) std::cout<<marks[i]<<' ';
delete[] marks;| Allocation | Release |
|---|---|
| new T | delete p |
| new T[n] | delete[] p |
Mismatch, double delete, interior pointer delete या non-new storage delete undefined behavior है। Ordinary collection के लिए std::vector<int> marks{72,88,91}; लें—size और cleanup automatic। vector तथा dynamic memory पढ़ें।
Objects और Destruction
class Student{
std::string name_;
public:
explicit Student(std::string n):name_(std::move(n)){
std::cout<<"Constructed: "<<name_<<'\n'; }
~Student(){std::cout<<"Destroyed: "<<name_<<'\n';}
void print()const{std::cout<<"Student: "<<name_<<'\n';}
};
Student* s=new Student{"Aman"};
s->print();
delete s;Construction पूरा होने के बाद pointer मिलता है। Delete पहले destructor, फिर storage deallocate करता है। Constructor throw हो तो new-expression obtained storage release करता है; members को RAII owner रखें। destructors देखें।
Failure और Advanced Forms
Throwing new
try{
auto* data=new int[huge_count];
delete[] data;
}catch(const std::bad_alloc&){ /* policy */ }Ordinary failure सामान्यतः bad_alloc throw करता है। केवल meaningful recovery boundary पर catch करें।
Nothrow
int* p=new(std::nothrow) int{7};
if(!p){ /* failure */ } else delete p;Nothrow allocation failure पर nullptr; object constructor के other exceptions फिर भी संभव।
Placement new
Supplied aligned storage में object construct करता है; ordinary delete नहीं लगती और explicit destruction/lifetime rules चाहिए। यह allocators जैसी expert infrastructure technique है, application default नहीं।
Ownership Rules
| Bug | Prevention |
|---|---|
| Leak | RAII owner |
| Double delete | Unique ownership |
| Use-after-free | Clear lifetime |
| Mismatch | Container/RAII |
| Ambiguous owner | Owner type/API में |
| Exception leak | Immediate RAII acquisition |
void process(const Student*); // non-owner
void take(std::unique_ptr<Student>); // transfer
Student& require(Student&); // borrowed non-nullRaw pointer/reference normally non-owning view हो। एक raw pointer से दो independent smart owners कभी न बनाएं। Standard members होने पर Rule of Zero अपनाएं।
RAII और Smart Pointers
auto student=std::make_unique<Student>("Aman");
student->print();
// scope end पर automatic destruction| Need | Preferred |
|---|---|
| Scoped value | Object by value |
| Dynamic sequence | vector/string |
| Exclusive owner | unique_ptr/make_unique |
| Real shared lifetime | shared_ptr/make_shared |
| Non-owning shared observation | weak_ptr when needed |
unique_ptr default dynamic owner है। shared_ptr केवल real shared graph में; cycles weak_ptr या ownership redesign से तोड़ें। Function केवल object use करता है तो reference लें, ownership pointer नहीं। smart pointers पढ़ें।
Mistakes और 100-Point Checklist
| Mistake | Correction |
|---|---|
| Manual ordinary array | vector/array/string |
| Early return leak | Immediate RAII |
| new[] + delete | Matching form/remove pair |
| Two owners | One owner, बाकी borrowers |
| shared_ptr default | Value/unique ownership |
| Placement new business code | Expert infrastructure only |
- Automatic object possible?
- Standard container possible?
- Owner type में visible?
- Unique ownership enough?
- Forms matched?
- Exceptions/returns safe?
- Borrow owner से shorter?
- Bounds explicit?
- Sanitizer tests?
- RAII internals के बाहर zero naked owning new/delete?
g++ -std=c++20 -Wall -Wextra -Wpedantic \
-fsanitize=address,undefined app.cppAuthoritative संदर्भ
Lifetime, matching और failure rules current working draft से verified हैं। Production guidance scoped objects, RAII containers और ownership types को naked new/delete पर prefer करती है।