Skip to content

Instantly share code, notes, and snippets.

@mpenick
Created May 23, 2017 15:03
Show Gist options
  • Select an option

  • Save mpenick/88f8b0574ea570051112a8df8cfa707d to your computer and use it in GitHub Desktop.

Select an option

Save mpenick/88f8b0574ea570051112a8df8cfa707d to your computer and use it in GitHub Desktop.
Overriding C++ Allocators
#include <new>
#include <iostream>
// Approach #1: Overload global operators
//
// Problem: Our allocator overrides/conflicts with the application's
// allocator when static linking.
void* operator new(size_t size) throw(std::bad_alloc) {
void* p = malloc(size);
printf("new %p\n", p);
return p;
}
void* operator new[](size_t size) throw(std::bad_alloc) {
void* p = malloc(size);
printf("new[] %p\n", p);
return p;
}
void operator delete(void* ptr) throw() {
printf("delete %p\n", ptr);
free(ptr);
}
void operator delete[](void* ptr) throw() {
printf("delete[] %p\n", ptr);
free(ptr);
}
class MyClass {
public:
MyClass() {
std::cout << "Constructor" << std::endl;
}
~MyClass() {
std::cout << "Destructor" << std::endl;
}
};
int main() {
std::cout << "Allocating single instance of POD type" << std::endl;
int* i = new int;
delete i;
std::cout << std::endl << "Allocating an array of instances of POD type" << std::endl;
int* ia = new int[4];
delete[] ia;
std::cout << std::endl << "sizeof(MyClass) = " << sizeof(MyClass) << std::endl;
std::cout << std::endl << "Allocating single instance of MyClass" << std::endl;
MyClass* c = new MyClass;
delete c;
std::cout << std::endl << "Allocating an array of instances of MyClass" << std::endl;
MyClass* ca = new MyClass[4];
delete[] ca;
}
#include <new>
#include <iostream>
// Approach #2: Use class-level overloads
//
// Problem: This doesn't work for POD types.
//
// Problem: It forces all classes to have to subclass "Allocated" to work
// properly. This can be error prone because it's hard to ensure that all
// existing and future classes will do this correctly.
//
class Allocated {
public:
void* operator new(size_t size) {
void* p = malloc(size);
printf("Allocated::new %p\n", p);
return p;
}
void* operator new[](size_t size) {
void* p = malloc(size);
printf("Allocated::new[] %p\n", p);
return p;
}
void operator delete(void* ptr) {
printf("Allocated::delete %p\n", ptr);
free(ptr);
}
void operator delete[](void* ptr) {
printf("Allocated::delete[] %p\n", ptr);
free(ptr);
}
};
class MyClass : public Allocated {
public:
MyClass() {
std::cout << "Constructor" << std::endl;
}
~MyClass() {
std::cout << "Destructor" << std::endl;
}
};
int main() {
std::cout << "Allocating single instance of POD type" << std::endl;
int* i = new int;
delete i;
std::cout << std::endl << "Allocating an array of instances of POD type" << std::endl;
int* ia = new int[4];
delete[] ia;
std::cout << std::endl << "sizeof(MyClass) = " << sizeof(MyClass) << std::endl;
std::cout << std::endl << "Allocating single instance of MyClass" << std::endl;
MyClass* c = new MyClass;
delete c;
std::cout << std::endl << "Allocating an array of instances of MyClass" << std::endl;
MyClass* ca = new MyClass[4];
delete[] ca;
}
#include <new>
#include <iostream>
// Approach #3: Use placement new overload
//
// Problem: This doesn't work. Our overloaded `operator delete` and `operator
// delete[]` functions are never called and there's no syntax to call them e.g.
// `delete (Alloc) foo` or `delete[] (Alloc) foo`. The syntax `operator delete(ptr, ALLOC)`
// exists but this doesn't work for the array syntax `operator delete[](ptr, ALLOC)` because
// `new SomeType[]` can add extra memory keep track of the number of elements in the array so
// the elements can be deconstructed properly.
struct AllocTag { };
const AllocTag ALLOC = { };
void* operator new(size_t size, AllocTag) throw(std::bad_alloc) {
void* p = malloc(size);
printf("new (ALLOC) %p\n", p);
return p;
}
void* operator new[](size_t size, AllocTag) throw(std::bad_alloc) {
void* p = malloc(size);
printf("new (ALLOC) [] %p\n", p);
return p;
}
void operator delete(void* ptr, AllocTag) throw() {
printf("delete (ALLOC) %p\n", ptr);
free(ptr);
}
void operator delete[](void* ptr, AllocTag) throw() {
printf("delete (ALLOC) [] %p\n", ptr);
free(ptr);
}
class MyClass {
public:
MyClass() {
std::cout << "Constructor" << std::endl;
}
~MyClass() {
std::cout << "Destructor" << std::endl;
}
};
int main() {
std::cout << "Allocating single instance of POD type" << std::endl;
int* i = new (ALLOC) int;
delete i;
std::cout << std::endl << "Allocating an array of instances of POD type" << std::endl;
int* ia = new (ALLOC) int[4];
delete[] ia;
std::cout << std::endl << "sizeof(MyClass) = " << sizeof(MyClass) << std::endl;
std::cout << std::endl << "Allocating single instance of MyClass" << std::endl;
MyClass* c = new (ALLOC) MyClass;
delete c;
std::cout << std::endl << "Allocating an array of instances of MyClass" << std::endl;
MyClass* ca = new (ALLOC) MyClass[4];
delete[] ca;
}
#include <new>
#include <iostream>
#include <stdint.h>
// Approach #4: Create a custom allocator
//
// Problem: Complexity
class Memory {
public:
template <class T>
static T* allocate() {
T* ptr = reinterpret_cast<T*>(malloc(sizeof(T)));
return new (ptr) T();
}
template <class T, class Arg1>
static T* allocate(const Arg1 arg1) {
T* ptr = reinterpret_cast<T*>(malloc(sizeof(T)));
return new (ptr) T(arg1);
}
template <class T, class Arg1, class Arg2>
static T* allocate(const Arg1& arg1, const Arg2& arg2) {
T* ptr = reinterpret_cast<T*>(malloc(sizeof(T)));
return new (ptr) T(arg1, arg2);
}
template <class T, class Arg1, class Arg2, class Arg3>
static T* allocate(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3) {
T* ptr = reinterpret_cast<T*>(malloc(sizeof(T)));
return new (ptr) T(arg1, arg2, arg3);
}
template <class T, class Arg1, class Arg2, class Arg3, class Arg4>
static T* allocate(const Arg1& arg1, const Arg2& arg2, const Arg3& arg3, const Arg4& arg4) {
T* ptr = reinterpret_cast<T*>(malloc(sizeof(T)));
return new (ptr) T(arg1, arg2, arg3, arg4);
}
template <class T>
static void deallocate(T* ptr) {
ptr->~T();
free(ptr);
}
static void* malloc(size_t size) {
// The alloc callback would be used here
return ::malloc(size);
}
static void* realloc(void* ptr, size_t size) {
// The alloc callback would be used here
return ::realloc(ptr, size);
}
static void* calloc(size_t count, size_t size) {
// The alloc callback would be used here
return ::calloc(size, count);
}
static void free(void* ptr) {
// The alloc callback would be used here
return ::free(ptr);
}
};
template <class T>
class DynamicArray {
public:
DynamicArray(size_t n)
: n_(n)
, elements_(reinterpret_cast<T*>(Memory::malloc(sizeof(T) * n_))) {
for (size_t i = 0; i < n_; ++i) {
new (elements_ + i) T();
}
}
DynamicArray(const DynamicArray& other)
: n_(other.n_)
, elements_(reinterpret_cast<T*>(Memory::malloc(sizeof(T) * n_))) {
for (size_t i = 0; i < n_; ++i) {
new (elements_ + i) T(other[i]);
}
}
~DynamicArray() {
for (size_t i = 0; i < n_; ++i) {
(elements_ + i)->~T();
}
Memory::free(elements_);
}
T& operator[](size_t index) {
return *(elements_ + index);
}
const T& operator[](size_t index) const {
return *(elements_ + index);
}
T* data() { return elements_; }
const T* data() const { return elements_; }
private:
size_t n_;
T* elements_;
};
class MyClass {
public:
MyClass() {
std::cout << "Constructor" << std::endl;
}
~MyClass() {
std::cout << "Destructor" << std::endl;
}
private:
int a;
char b;
};
int main() {
std::cout << "Allocating single instance of POD type" << std::endl;
int* i = Memory::allocate<int>();
Memory::deallocate(i);
std::cout << std::endl << "Allocating an array of instances of POD type" << std::endl;
DynamicArray<int> ia(4);
std::cout << std::endl << "sizeof(MyClass) = " << sizeof(MyClass) << std::endl;
std::cout << std::endl << "Allocating single instance of MyClass" << std::endl;
MyClass* c = Memory::allocate<MyClass>();
Memory::deallocate(c);
std::cout << std::endl << "Allocating an array of instances of MyClass" << std::endl;
DynamicArray<MyClass> ca0(4);
DynamicArray<MyClass> ca1(ca0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment