I have implemented a C++11 compatible linear or arena allocator. The code follows.
linear_allocator.hpp:
#pragma once
#include <cstddef>
#include <cassert>
#include <new>
#include "aligned_mallocations.hpp"
template <typename T>
class LinearAllocator
{
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
//using propagate_on_container_copy_assignment = std::true_type;
//using propagate_on_container_move_assignment = std::true_type;
//using propagate_on_container_swap = std::true_type;
LinearAllocator(std::size_t count = 64)
: m_memUsed(0),
m_memStartAddress(nullptr)
{
allocate(count);
}
~LinearAllocator()
{
clear();
}
template <class U>
LinearAllocator(const LinearAllocator<U>&) noexcept
{}
/// \brief allocates memory equal to # count objects of type T
pointer allocate(std::size_t count)
{
if (count > std::size_t(-1) / sizeof(T))
{
throw std::bad_alloc{};
}
if (m_memStartAddress != nullptr)
{
alignedFree(m_memStartAddress);
}
m_memUsed = count * sizeof(T);
m_memStartAddress = static_cast<pointer>(alignedMalloc(m_memUsed, alignof(T)));
return m_memStartAddress;
}
/// \brief deallocates previously allocated memory
/// \brief Linear/arena allocators do not support free() operations. Use clear() instead.
void deallocate([[maybe_unused]] pointer p, [[maybe_unused]] std::size_t count) noexcept
{
//assert(false);
clear();
}
/// \brief simply resets memory
void clear()
{
if (m_memStartAddress != nullptr)
{
alignedFree(m_memStartAddress);
m_memStartAddress = nullptr;
}
this->m_memUsed = 0;
}
/// \brief GETTERS
pointer getStartAddress() const
{
return this->m_memStartAddress;
}
std::size_t getUsedMemory() const
{
return this->m_memUsed;
}
private:
std::size_t m_memUsed;
pointer m_memStartAddress;
};
template <class T, class U>
bool operator==(const LinearAllocator<T> &, const LinearAllocator<U> &)
{
return true;
}
template <class T, class U>
bool operator!=(const LinearAllocator<T> &, const LinearAllocator<U> &)
{
return false;
}
Don't worry about alignedMalloc and alignedFree. They are correct.
This is my test program (linear_allocator.cpp):
#include "linear_allocator.hpp"
#include <vector>
#include <deque>
#include <iostream>
#include <string>
#include <typeinfo>
int main()
{
[[maybe_unused]]
LinearAllocator<int> a{1024};
std::cout << a.getStartAddress() << '\n';
std::cout << a.getUsedMemory() << '\n';
std::vector<std::string, LinearAllocator<std::string>> v;
v.reserve(100);
std::cout << "Vector capacity = " << v.capacity() << '\n';
//std::cout << v.get_allocator().getStartAddress() << '\n';
//std::cout << v.get_allocator().getUsedMemory() << '\n';
v.push_back("Hello");
v.push_back("w/e");
v.push_back("whatever");
v.push_back("there is ist sofi j");
v.push_back("wisdom");
v.push_back("fear");
v.push_back("there's more than meets the eye");
for (const auto &s : v)
{
std::cout << s << '\n';
}
std::cout << typeid(v.get_allocator()).name() << '\n';
std::deque<int, LinearAllocator<int>> dq;
dq.push_back(23);
dq.push_back(90);
dq.push_back(38794);
dq.push_back(7);
dq.push_back(0);
dq.push_back(2);
dq.push_back(13);
dq.push_back(24323);
dq.push_back(0);
dq.push_back(1234);
for (const auto &i : dq)
{
std::cout << i << '\n';
}
std::cout << typeid(dq.get_allocator()).name() << '\n';
}
Compiling with g++ -std=c++17 -O2 -march=native -Wall linear_allocator.cpp -o linear_allocator.gpp.exe and running linear_allocator.gpp.exe gives output:
0x4328b8
4096
Vector capacity = 100
Hello
w/e
whatever
there is ist sofi j
wisdom
fear
there's more than meets the eye
15LinearAllocatorINSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEE
As you can see deque's output isn't there at all. If I uncomment these 2 lines:
//std::cout << v.get_allocator().getStartAddress() << '\n';
//std::cout << v.get_allocator().getUsedMemory() << '\n';
vector's output will also not be displayed.
Compilation with MSVS cl gives the following output:
000000B47A1CAF88
4096
which is even worse.
There must be something I am missing as there appears to be UB, but I am unable to pinpoint where that is. My allocator design was based on C++11 + guidelines. I wonder what am I doing wrong.
While an allocator takes care of providing and releasing memory for storing container's data, it still does so only on container's request. That is, the actual management of the provided storage (in particular, its lifetime) is still on the container's side. Imagine what happens when a vector performs a relocation of its elements:
A new chunk of memory, greater by a given factor than the current (old) one, is requested.
The elements stored in the "old" chunk are copied/moved to the new chunk.
Only then, the "old" memory chunk can be released.
In your implementation, there can be only one memory chunk active at a time -- the old memory chunk is released before a new one is allocated (specifically, this happens when a container only requests a new chunk of memory, where the elements could be relocated to). You invoke UB already when the vector tries to relocate elements from the previous storage, because the memory where they lived has already been invalidated.
Additionally, by not providing a copy-constructor for your allocator type, the compiler-provided implementation performs a shallow copy (i.e., it copies the pointer, not the data stored under that address), which is then released in the destructor. That is, the call:
v.get_allocator()
will make a shallow copy of the allocator, creating a prvalue of your allocator type, and release the stored pointer as soon as the temporary object ends its lifetime (i.e., at the end of full statement including the cout call), leading to a double call to alignedFree on the same pointer.
Related
Currently, I'm storing a collection of std::unique_ptrs to heap allocated objects of a polymorphic type:
struct Foo {
virtual ~Foo() = default;
};
Collection<std::unique_ptr<Foo>> foos;
The basic interface I need is putting / taking owners of Foo to / from foos. The objects stored in foos are never supposed to be nullptr so I'd like to replace runtime assert(owner_taken) with compile-time checks. Moreover, I would like to be capable of using non-null owners in the context where a nullable one may be expected.
Probably, I need to store something like unique_ref but how would I extract one from foos? I don't want a copy, I want the stored object itself, so owner->clone() isn't a solution. Neither I can std::move(owner) because the state of a "unique reference" would be invalid afterwards.
Is there a clean design decision?
Is there a never-null unique owner of heap allocated objects?
There is no such type in the standard library.
It is possible to implement such type. Simply define a type with unique_ptr member and mark the default constructor deleted. You can mark constructor from std::nullptr_t deleted also so that construction from nullptr will be prevented at compile time.
Whether you construct from an external pointer, or allocate in the constructor, there is no alternative for checking for null at runtime.
Reading your question, I interpret the following requirements:
You don't want to copy or move the object itself (Foo)
You don't want a wrapper which has some sort of empty state which excludes move semantics
The object itself (Foo) should be stored on the heap such that its lifetime is independent of the control flow
The object itself (Foo) should be deleted once it is not used any more
As construction / destruction, copy and move are the only ways to get objects into / out of a container, the only thing left is a wrapper object which is copied when moved into / out of the container.
You can create such an object yourself as follows:
// LICENSE: MIT
#include <memory>
#include <utility>
template<typename T>
class shared_ref {
public:
template<typename ...Args>
shared_ref(Args&&... args)
: data{new T(std::forward<Args>(args)...)}
{}
shared_ref(shared_ref&&) = delete;
shared_ref& operator=(shared_ref&&) = delete;
T& get() noexcept {
return *data;
}
const T& get() const noexcept {
return *data;
}
operator T&() noexcept {
return get();
}
operator const T&() const noexcept {
return get();
}
void swap(shared_ref& other) noexcept {
return data.swap(other);
}
private:
std::shared_ptr<T> data;
};
template<class T>
void swap(shared_ref<T>& lhs, shared_ref<T>& rhs) noexcept {
return lhs.swap(rhs);
}
I leave it as an exercise to the reader to add support for Allocator, Deleter, operator[], implicit conversion contructor to base classes.
This can then be used as follows:
#include <iostream>
int main() {
shared_ref<int> r; // default initialized
std::cout << "r=" << r << std::endl;
r = 5; // type conversion operator to reference
std::cout << "r=" << r << std::endl;
shared_ref<int> s{7}; // initialized with forwarded arguments
std::cout << "s=" << s << std::endl;
std::swap(r, s);
std::cout << "r=" << r << ", " << "s=" << s << std::endl;
s = r; // copy assignment operator
std::cout << "s=" << s << std::endl;
const shared_ref<int> t{s}; // copy constructor
std::cout << "t=" << t << std::endl;
//t = 8; // const ref from a const object is immutable
return 0;
}
I was trying to understand a bit better std::allocator in C++, I came across this question, and it seems to me there's actually only one allocator class typically used by containers (like std::vector) my question is how is such allocator implemented? Is it like a stack which is periodically re-allocated? If not how is it actually implemented?
The default allocator is std::allocator, and just uses ::operator new as and when required, so nothing special. It's more or less the same as doing new and delete yourself for each object needed. You can read more about it under [default.allocator] in the standard.
The allocator "interface" (really just a set of requirements, enforced during template instantiation) is a wrapper around this process, allowing alternative memory provisioning approaches to be employed.
For example, alternative allocators that you may provide could implement a memory pool or something else that's specific to your needs, cutting down on honest-to-goodness dynamic allocations.
Standard containers have, as well as their element type, an allocator type as template argument (you usually don't notice this!) and this is how you select alternative implementations for use with that container.
In these cases you'll generally be pre-allocating some big chunk of memory then dishing out little chunks as and when. In that sense, such an implementation could be considered a sort of "heap within a heap", but really there's no reason you need to give it heap semantics at all. It only needs to abide by the requirements of the concept Allocator.
Mr Josuttis has put a (boring) example up at http://www.josuttis.com/cppcode/allocator.html; I reproduce it here:
/* The following code example is taken from the book
* "The C++ Standard Library - A Tutorial and Reference"
* by Nicolai M. Josuttis, Addison-Wesley, 1999
*
* (C) Copyright Nicolai M. Josuttis 1999.
* Permission to copy, use, modify, sell and distribute this software
* is granted provided this copyright notice appears in all copies.
* This software is provided "as is" without express or implied
* warranty, and with no claim as to its suitability for any purpose.
*/
#include <limits>
#include <iostream>
namespace MyLib {
template <class T>
class MyAlloc {
public:
// type definitions
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// rebind allocator to type U
template <class U>
struct rebind {
typedef MyAlloc<U> other;
};
// return address of values
pointer address (reference value) const {
return &value;
}
const_pointer address (const_reference value) const {
return &value;
}
/* constructors and destructor
* - nothing to do because the allocator has no state
*/
MyAlloc() throw() {
}
MyAlloc(const MyAlloc&) throw() {
}
template <class U>
MyAlloc (const MyAlloc<U>&) throw() {
}
~MyAlloc() throw() {
}
// return maximum number of elements that can be allocated
size_type max_size () const throw() {
return std::numeric_limits<std::size_t>::max() / sizeof(T);
}
// allocate but don't initialize num elements of type T
pointer allocate (size_type num, const void* = 0) {
// print message and allocate memory with global new
std::cerr << "allocate " << num << " element(s)"
<< " of size " << sizeof(T) << std::endl;
pointer ret = (pointer)(::operator new(num*sizeof(T)));
std::cerr << " allocated at: " << (void*)ret << std::endl;
return ret;
}
// initialize elements of allocated storage p with value value
void construct (pointer p, const T& value) {
// initialize memory with placement new
new((void*)p)T(value);
}
// destroy elements of initialized storage p
void destroy (pointer p) {
// destroy objects by calling their destructor
p->~T();
}
// deallocate storage p of deleted elements
void deallocate (pointer p, size_type num) {
// print message and deallocate memory with global delete
std::cerr << "deallocate " << num << " element(s)"
<< " of size " << sizeof(T)
<< " at: " << (void*)p << std::endl;
::operator delete((void*)p);
}
};
// return that all specializations of this allocator are interchangeable
template <class T1, class T2>
bool operator== (const MyAlloc<T1>&,
const MyAlloc<T2>&) throw() {
return true;
}
template <class T1, class T2>
bool operator!= (const MyAlloc<T1>&,
const MyAlloc<T2>&) throw() {
return false;
}
}
And usage:
#include <vector>
#include "myalloc.hpp"
int main()
{
// create a vector, using MyAlloc<> as allocator
std::vector<int,MyLib::MyAlloc<int> > v;
// insert elements
// - causes reallocations
v.push_back(42);
v.push_back(56);
v.push_back(11);
v.push_back(22);
v.push_back(33);
v.push_back(44);
}
Allocators just provide a policy for allocating memory, deallocating memory, constructing object and destroying them.
They do not provide a policy to reallocate memory (increasing the size of a previously allocated memory region). So in all containers, if the memory must be increased:
either the container allocate a new memory chunk,
or as vector does, allocate a new larger memory region, copy the old elements inside it and then release the previous memory region.
Does the std::vector in C++ compact bools? I mean I have read that std::vector can combine 8 booleans into 1 byte. However, when I tried this code in visual studio,
#include <vector>
#include <iostream>
using namespace std;
int main()
{
vector<bool> array {true, false, false, true, true,false,false,true};
cout << sizeof(array) << endl;
cout << sizeof(array[0]) << endl;
getchar();
return 0;
}
it printed:
24
16
while in another IDE, such as codeblocks, it printed 20 and 8.
I don't quite get what it does with booleans here.
Does the std::vector in C++ compact bools?
Yes, it is allowed to do so, and typically does.
I don't quite get what it does with booleans here.
You actually don't get what array[0] evaluates to.
It does not evaluate to a bit. It evaluates to a proxy object that correctly handles both conversion to bool and assignment from bool.
the sizeof this proxy does not have much significance. It is not the size of a bit or a bool. It's the size of an object programmed to act on a specific bit.
std::vector usually uses dynamic allocation internally by default. If you define your own allocator that tracks actual allocation size, you'll see that the number of bytes allocated for vector<bool> implies values are stored as bits:
#include <vector>
#include <iostream>
template<typename T>
class my_allocator : public std::allocator<T> {
public:
T * allocate(const size_t count) {
std::cout << "Allocated " << count << " * " << typeid(T).name() << std::endl;
std::cout << "Total size: " << count * sizeof(T) << std::endl;
return std::allocator<T>::allocate(count);
}
T * allocate(const size_t count, const void *) {
return allocate(count);
}
template<typename U>
struct rebind {
typedef my_allocator<U> other;
};
my_allocator() noexcept {};
my_allocator(const my_allocator<T>&) noexcept = default;
template<typename Other>
my_allocator(const my_allocator<Other>&) noexcept {}
};
int main() {
std::vector<int, my_allocator<int>> v1 { 0 };
std::vector<bool, my_allocator<bool>> v2 { 0 };
v1.reserve(100);
v2.reserve(100);
return 0;
}
Relevant output:
Allocated 100 * int
Total size: 400
Allocated 4 * unsigned int
Total size: 16
Demo: https://wandbox.org/permlink/WHTD0k3sMvd3E4ag
How can you initialize a container to a class with no default? How can you create the array of pointers required without using new or a method which calls the default constructor of a class?
#include <string>
#include <iostream>
#include <vector>
#include "MyVector.h"
class NoDefault {
public:
//NoDefault():value(0){};
NoDefault(const int& value) : value(value) {}
int value;
};
std::ostream& operator<<(std::ostream& out, const NoDefault& noDefault) {
out << noDefault.value;
return out;
}
int main() {
MyVector<int> intVec(10, 99);
MyVector<std::string> stringVec(5, "hi");
MyVector<NoDefault> noDefaultVec(4, -3);
std::vector<std::vector<std::string>> tempRealVec({{"hi", "bye"}, {"sly",
"guy", "why"}});
MyVector<MyVector<std::string> > tempMyVec(tempRealVec);
MyVector<MyVector<MyVector<std::string> > > vecVecVecStringVec(2, tempMyVec);
std::cout << "intVec = " << intVec << std::endl;
std::cout << "stringVec = " << stringVec << std::endl;
std::cout << "noDefaultVec = " << noDefaultVec << std::endl;
std::cout << "vecVecVecStringVec = " << vecVecVecStringVec << std::endl;
std::cout << "hello" << std::endl;
return 0;
}
This is the constructor
template <typename T>
MyVector<T>::MyVector(const unsigned int& numElements,const T& value):data_(new
T[numElements]),size_(numElements)
{
for(int i = 0; i < size_; i++)
{
if(std::is_same<T,int>::value)
data_[i]=T(value);
else if(std::is_same<T,std::string>::value)
data_[i]=T(value);
else if(std::is_same<T,MyVector>::value)
data_[i]=T(value);
else if(std::is_same<T,NoDefault>::value)
data_[i]=T(value);
}
}
the error thrown because of the use of new in initializer list is no matching function for call to NoDefault::NoDefault(), even if i create that constructor, which would defeat the purpose. It then prints the vector with the default value not the value given as the second argument in the call to MyVector.
You can use placement new. Placement new allows you to construct an object in a specific memory location, so the steps look something like this:
Allocate raw memory (malloc or ::operator new both work)
Construct your object in this raw memory (new (ptr) NoDefault{args})
This is roughly how regular new works (allocates memory via ::operator new, then uses placement new to construct the object). I'm not sure if stdl containers like vector are required to use this method, but I'd be a bit surprised if they didn't.
The big caveat (and it is a big one) is that when using placement new you have to manually invoke the destructor (the only time I'm aware of where you intentionally invoke destructors). After the destructors have been called, you're free to release the allocated memory using the appropriate method (e.g., free or ::operator delete).
More information is available from the C++ FAQ.
I'm currently writing a logging class (just for practice) and ran into an issue. I have two classes: The class Buffer acts as a temporary buffer and flushes itself in it's destructor. And the class Proxy that returns a Buffer instance, so I don't have to write Buffer() all the time.
Anyways, here is the code:
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
class Buffer
{
private:
std::stringstream buf;
public:
Buffer(){};
template <typename T>
Buffer(const T& v)
{
buf << v;
std::cout << "Constructor called\n";
};
~Buffer()
{
std::cout << "HEADER: " << buf.str() << "\n";
}
Buffer(const Buffer& b)
{
std::cout << "Copy-constructor called\n";
// How to get rid of this?
};
Buffer(Buffer&&) = default;
Buffer& operator=(const Buffer&) & = delete;
Buffer& operator=(Buffer&&) & = delete;
template <typename T>
Buffer& operator<<(const T& v)
{
buf << v;
return *this;
}
};
class Proxy
{
public:
Proxy(){};
~Proxy(){};
Proxy(const Proxy&) = delete;
Proxy(Proxy&&) = delete;
Proxy& operator=(const Proxy&) & = delete;
Proxy& operator=(Proxy&&) & = delete;
template <typename T>
Buffer operator<<(const T& v) const
{
if(v < 0)
return Buffer();
else
return Buffer(v);
}
};
int main () {
Buffer(Buffer() << "Test") << "what";
Buffer() << "This " << "works " << "fine";
const Proxy pr;
pr << "This " << "doesn't " << "use the copy-constructor";
pr << "This is a " << std::setw(10) << " test";
return 0;
}
Here is the output:
Copy-constructor called
HEADER: what
HEADER: Test
HEADER: This works fine
Constructor called
HEADER: This doesn't use the copy-constructor
Constructor called
HEADER: This is a test
The code does exactly what I want but it depends on RVO. I read multiple times that you should not rely on RVO so I wanted to ask how I can:
Avoid RVO completely so that the copy constructor is called every time
Avoid the copy constructor
I already tried to avoid the copy constructor by returning a reference or moving but that segfaults. I guess thats because the temporary in Proxy::operator<< is deleted during return.
I'd also be interested in completely different approaches that do roughly the same.
This seems like a contrived problem: Firstly, the code works whether RVO is enabled or disabled (you can test it by using G++ with the no-elide-constructors flag). Secondly, the way you are designing the return of a Buffer object for use with the << operator can only be done by copying†: The Proxy::operator<<(const T& v) function creates a new Buffer instance on the stack, which is then deleted when you leave the function call (i.e. between each concatenation in pr << "This " << "doesn't " << "use the copy-constructor";); This is why you get a segmentation fault when trying to reference this object from outside the function.
Alternatively, you could define a << operator to use dynamic memory by e.g. returning a unique_ptr<Buffer>:
#include <memory>
...
std::unique_ptr<Buffer> operator<<(const T& v) const
{
if(v < 0)
return std::unique_ptr<Buffer>(new Buffer());
else
return std::unique_ptr<Buffer>(new Buffer(v));
}
However, your original concatenation statements won't be compilable, then, because Proxy::operator<<(const T& v) now returns an object of type std::unique_ptr<Buffer> rather than Buffer, meaning that this returned object doesn't have its own Proxy::operator<<(const T& v) function defined and so multiple concatenations won't work without first explicitly de-referencing the returned pointer:
const Proxy pr;
std::unique_ptr<Buffer> pb = pr << "This ";
// pb << "doesn't " << "use the copy-constructor"; // This line doesn't work
*pb << "doesn't " << "use the copy-constructor";
In other words, your classes rely inherently on copying and so, if you really want to avoid copying, you should throw them away and completely re-design your logging functionalities.
† I'm sure there's some black-magic voodoo which can be invoked to make this possible --- albeit at the cost of one's sanity.