Memory Allocation: new vs. malloc
The distinction betwean new and malloc lies at the core of C++’s object model:
- Language integration:
newis an operator built into C++, whilemallocis a C-standard library function declared in<cstdlib>. - Unit of allocation:
newallocates memory sized for a specific type (e.g.,new Widget), where asmallocrequests raw bytes (e.g.,malloc(sizeof(Widget))). - Initialization:
newinvokes constructors, enabling type-safe initialization;mallocreturns uninitialized memory. - Portability & semantics:
newis universally supported across conforming C++ implementations. In contrast,mallocmay be unavailable or unsafe in freestanding environments (e.g., embedded kernels or bootloaders). - OOP compatibility: Only
newproperly constructs objects — usingmallocbypasses construction logic, risking undefined behavior if used with non-POD types.
Deallocation: delete vs. free
Correspondingly, deallocation must match allocation style:
deleteensures destructors run before releasing memory;freeperforms no cleanup.deleteis standard across all C++ toolchains;freelacks guarantees in constrained execution contexts.- Mismatched pairs (
malloc+delete, ornew+free) result in undefined behavior — including memory corruption or silent leaks.
#include <iostream>
#include <cstdlib>
struct ResourceHolder {
int* data;
ResourceHolder() : data(new int(42)) {
std::cout << "ResourceHolder constructed\n";
}
~ResourceHolder() {
delete data;
std::cout << "ResourceHolder destroyed\n";
}
};
int main() {
// Correct: constructor/destructor invoked
ResourceHolder* safe = new ResourceHolder();
delete safe;
// Dangerous: no constructor called → data uninitialized
ResourceHolder* unsafe = static_cast<ResourceHolder*>(malloc(sizeof(ResourceHolder)));
// ... using 'unsafe' here risks undefined behavior
// Also dangerous: destructor skipped → memory leak
free(unsafe);
return 0;
}
Virtual Functions and Object Construction/Destruction
Virtual dispatch relies on runtime type information stored in vtables — but that infrastructure isn’t fully available during construction or destruction:
- Constructors cannot be virtual: The vtable pointer is not yet set up when a constructor begins executing. Hence, virtual calls inside constructors resolve statically to the current class’s implementation.
- Destructors should often be virtual: When deleting through a base-class pointer, a virtual destructor ensures the correct derived-class destructor runs first, preventing resource leaks and slicing issues.
- No dynamic dispatch in ctors/dtors: Even if overridden, virtual function calls inside these functions behave as if they were non-virtual — only the version defined in the currently constructing/destroying class executes.
#include <iostream>
class Animal {
public:
Animal() {
std::cout << "Animal::Animal()\n";
speak(); // Static bind → Animal::speak()
}
virtual ~Animal() {
std::cout << "Animal::~Animal()\n";
speak(); // Static bind → Animal::speak()
}
virtual void speak() { std::cout << "Animal speaks\n"; }
};
class Dog : public Animal {
public:
Dog() {
std::cout << "Dog::Dog()\n";
speak(); // Dynamic bind possible → Dog::speak()
}
~Dog() override {
std::cout << "Dog::~Dog()\n";
speak(); // Dynamic bind possible → Dog::speak()
}
void speak() override { std::cout << "Dog barks\n"; }
};
int main() {
Animal* pet = new Dog();
delete pet; // Requires virtual ~Animal() to avoid UB
}
Type-Safe Downcasting with dynamic_cast
dynamic_cast enables safe, runtime-checked conversions between polymorphic types:
- Requires at least one virtual function in the involved classes (typically a virtual destructor suffices).
- For pointers: returns
nullptron failure; safe too test before dereferencing. - For references: throws
std::bad_caston failure — requires exception handling. - Only valid along inheritance hierarchies (upcasts are implicit; downcasts require verification).
#include <iostream>
#include <typeinfo>
class Shape { public: virtual ~Shape() = default; };
class Circle : public Shape {};
class Rectangle : public Shape {};
int main() {
Shape* s = new Circle();
// Safe downcast
Circle* c = dynamic_cast<Circle*>(s);
if (c) {
std::cout << "Cast succeeded: Circle detected\n";
} else {
std::cout << "Cast failed\n";
}
// This would fail
Rectangle* r = dynamic_cast<Rectangle*>(s);
if (!r) {
std::cout << "Not a Rectangle\n";
}
delete s;
}