Skip to content

Instantly share code, notes, and snippets.

@kopp
Created July 18, 2021 21:28
Show Gist options
  • Save kopp/2e25e83676cbccfba47c3ae3ea337e5a to your computer and use it in GitHub Desktop.
Save kopp/2e25e83676cbccfba47c3ae3ea337e5a to your computer and use it in GitHub Desktop.
Demo of how to use a unique_ptr with a custom allocator.
/**
* Demo of how to use a unique_ptr with a custom allocator.
*
* Unlicensed 2021 kopp
*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <https://unlicense.org>
*/
#include <array>
#include <set>
#include <memory>
#include <exception>
#include <stdexcept>
#include <iostream>
#include <iomanip>
struct Data
{
int i;
};
/**
* Simple memory pool of fixed size that can be used as "custom allocator".
*/
class Pool
{
private:
static constexpr std::size_t size{1};
std::array<Data, size> data;
std::set<Data*> available;
public:
Pool()
{
for (auto& elem : data) {
available.insert(std::addressof(elem));
}
}
Data* allocate()
{
if (available.empty()) {
throw std::bad_alloc();
}
else {
const auto next_free_address_iter = available.begin();
available.erase(next_free_address_iter);
std::cout << "Allocating " << std::hex << static_cast<void*>(*next_free_address_iter) << "\n";
return *next_free_address_iter;
}
}
void deallocate(Data* d) {
const auto begin_ptr = std::addressof(data.front());
const auto back_ptr = std::addressof(data.back());
const bool is_within_data = begin_ptr <= d and d <= back_ptr;
// TODO: check alignment -- now d+1 would be in this check but is not a valid address in data
// or is this handled by the alignment requirements for Data*?
if (is_within_data) {
auto available_iter_of_pointer_to_delete = available.find(d);
if (available_iter_of_pointer_to_delete == available.end()) {
std::cout << "Freeing " << std::hex << static_cast<void*>(d) << "\n";
available.insert(d);
}
else {
throw std::invalid_argument("This value is available, i.e. was not allocated.");
}
}
else {
throw std::out_of_range("Given pointer is not part of this pool.");
}
}
};
namespace std {
/** Template specialization of default_delete for Data to make it use the pool's
* deallocate function for delete.
*/
template<>
struct default_delete<Data>
{
Pool& pool_;
constexpr default_delete() noexcept = default;
default_delete(Pool& pool) noexcept
: pool_(pool)
{
}
void operator() (Data* ptr)
{
pool_.deallocate(ptr);
}
};
} // namespace std
int main()
{
Pool pool;
// raw pointers
auto* a = pool.allocate();
pool.deallocate(a);
// deleter as template argument of unique_ptr
// Drawback: If an API is using `unique_ptr<Data>`, this is incompatible.
// Benefit: It is possible to use Data with different allocators in unique_ptr's.
const auto deleter_for_this_pool = [&pool](Data* d) { pool.deallocate(d); };
{
auto b = std::unique_ptr<Data, decltype(deleter_for_this_pool)>(pool.allocate(), deleter_for_this_pool);
}
// use overwritten default_delete so that there is no need to change type of the unique_ptr
// Benefit: If an API is using `unique_ptr<Data>` it does not neet to
// know/be told that this is using a custom deallocator.
// Drawback: Now Data can only be used with this custom allocator in unique_ptr's.
std::default_delete<Data> default_deleter_for_this_pool(pool);
{
auto c = std::unique_ptr<Data>(pool.allocate(), default_deleter_for_this_pool);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment