Dynamic Memory Allocation and Generic Programming in C++
Heap Allocation with new and delete
C++ replaces traditional C allocation routines with operators that seamlessly integrate object lifecycles.
Primitive Data Allocation
Fundamental types use straightforward syntax. Single items require new/delete, while contiguous blocks require new[]/delete[]. Mismatched pairs result in undefined behavior.
void allocate_fundamentals() {
int* single_item = new int; // Uninitialized
int* initialized = new int{42}; // Initialized via brace-init
int* sequence = new int[20]; // Contiguous block
delete single_item;
delete initialized;
delete[] sequence; // Brackets mandatory for arrays
}
Custom Object Management
The critical distinction from malloc/free is lifecycle automation. new reserves storage and immediately executes the constructor. Conversely, delete triggers the destructor before reclaiming memory.
class Device {
public:
explicit Device(int port = 0) : port_id(port) {
// Initialization routines
}
~Device() {
// Resource teardown
}
private:
int port_id;
};
void object_allocation() {
// malloc only allocates raw bytes, skipping constructors
Device* raw_storage = static_cast<Device*>(std::malloc(sizeof(Device)));
std::free(raw_storage);
// new handles construction automatically
Device* active = new Device{101};
delete active; // Destructor runs, then memory frees
// Arrays require element-wise construction
Device* cluster = new Device[5];
delete[] cluster;
}
Underlying Implementation
The language operators act as wrappers around global allocation routines: operator new and operator delete.
- Primitive Types: Functionally idantical to
malloc/free, except failure triggers astd::bad_allocexception rather than a null pointer. - Class Instances: The compiler splits the operation into two distinct steps:
operator newacquires raw memory (typically invokingmalloc).- The constructor initializes the object at that address.
- Array Operations:
operator new[]calculates the total byte requirement, stores element metadata, and invokes the constructor for each item. Deallocation reverses the process: destructors execute in reverse order, followed byoperator delete[].
Placement New
This syntax allows object construction within pre-existing memory buffers. It separates allocation from initialization.
Syntax: new (target_address) Type(constructor_args)
void manual_construction() {
void* buffer = std::malloc(sizeof(Device));
// Construct object directly in the buffer
Device* instance = new (buffer) Device{200};
instance->~Device(); // Manual destructor invocation required
std::free(buffer);
// Alternative using global operator
void* region = ::operator new(sizeof(Device));
Device* unit = new (region) Device{201};
unit->~Device();
::operator delete(region);
}
Generic Programming via Templates
Templates enable algorithms to remain independent of specific data types, eliminating redundant code duplication.
Function Templates A template defines a blueprint. The compiler generates type-specific functions during the translation phase based on invocation context.
template <typename DataType>
void invert(DataType& x, DataType& y) {
DataType temp = x;
x = y;
y = temp;
}
Note: typename and class are interchangeable for template parameters, but struct is not permitted.
Instantiation Methods
- Implicit Deduction: The compiler infers the type from provided arguments.
- Explicit Specification: The developer defines the type within angle brackets.
template <typename Val>
Val combine(Val a, Val b) {
return a + b;
}
void test_deduction() {
combine(8, 9); // Implicit: Val = int
combine<float>(5.5f, 2.0f); // Explicit: Val = float
// Mixed types cause deduction failure
// combine(7, 3.5); // Error: ambiguous Val
// Resolution A: Explicit template argument
combine<int>(7, 3.5); // 3.5 converts to 3
// Resolution B: Explicit cast
combine(7, static_cast<int>(3.5));
}
Overload Resolution Rules
- Regular functions and templates can share identifiers.
- Exact signature matches prioritize non-template functions.
- If the template yields a tighter type match without conversion, the compiler selects it.
- Template parameter deduction forbids implicit type conversions, unlike standard functions.
int precise_sum(int a, int b) { return a + b; }
template <class X, class Y>
X flexible_sum(X a, Y b) { return a + b; }
void resolution_demo() {
precise_sum(3, 4); // Calls regular function (exact match)
// precise_sum(3, 4.5); // Fails: no implicit conversion allowed for exact match
flexible_sum(3, 4); // X=int, Y=int
flexible_sum(3, 4.5); // X=int, Y=double, compiles successfully
}
Class Templates Structures and classes can also be parameterized. Defining members outside the declaration requires repeating the template header.
template <class Item>
class Container {
public:
explicit Container(size_t reserve = 10)
: storage(new Item[reserve]), count(0), max_items(reserve) {}
~Container();
void insert(const Item& value);
Item& fetch(size_t index) {
return storage[index];
}
private:
Item* storage;
size_t count;
size_t max_items;
};
template <class Item>
Container<Item>::~Container() {
delete[] storage;
count = 0;
max_items = 0;
}
// Instantiation requires explicit type specification
Container<int> integer_cache;
Container<double> numeric_buffer;
The base template identifier is not a valid type until specialized with template arguments.