I am trying to create a framework for applications to use in my microprocessors. I am using Arduino IDE to compile and deploy the programs.
Since the microprocessors often have low heap memory, I want to only use stack memory if possible.
Minimial example:
The whole example code can be seen here.
I will describe the parts I think is most interesting.
iMinExApplication (interface):
class iMinExApplication
{
public:
virtual void initialize() = 0; // pure virtual
virtual void execute() = 0; // pure virtual
virtual ~iMinExApplication() = default; // Virtual destructor
};
tMinExApplication (extension of interface, only used by the framework):
class tMinExApplication
{
public:
...
tMinExApplication(iMinExApplication* app, const char name[]) : App(app)
{
strcpy(Name, name);
};
...
void execute() { App->execute(); };
private:
iMinExApplication* App;
char Name[32];
};
tMinExCoordinator (master, calling added apps)
class tMinExCoordinator
{
public:
...
void addApp(iMinExApplication* app, const char name[])
{
tMinExApplication* tmpPtr = new tMinExApplication(app, name); // HERE!
Applications[++NumApps] = tmpPtr;
tmpPtr = nullptr;
};
...
void runApps()
{
for (auto& app : Applications) {
// Frequency check
// ...
app->execute();
}
};
private:
tMinExApplication* Applications[];
int NumApps;
};
tMyApp (user defined app, using the inherited interface)
class tMyApp : public iMinExApplication {
...
minExSketch (Arduino IDE sketch)
#include "tMinExClasses.hpp"
#include "tMyApp.hpp"
tMinExCoordinator coordinator{};
tMyApp tmpApp{};
void setup() {
Serial.begin(9600);
coordinator.addApp(&tmpApp, "TEST");
coordinator.initializeApps();
}
void loop() {
coordinator.runApps();
}
The above works. But the apps are allocated in heap memory, since it uses the keyword new ('HERE!' in the tMinExCoordinator class definition, line 57 in tMinExClasses.hpp).
I cannot seem to get it to work without it.
In what other way could I implement this, but only allocating memory in stack memory?
Requirements:
Stack memory allocation
The interface is to be used.
I have though of smart pointers, but am unsure if they use heap memory or not. Also, I wanted the minimal example as clean as possible.
I cannot seem to get it to work without it. In what other way could I implement this, but only allocating memory in stack memory?*
You can pre-allocate a byte array of sufficient size, and then use placement-new to construct objects inside of that array (see std::aligned_storage to help you with that). Polymorphism only requires pointers/references to work at runtime, not dynamic alllocations.
template<std::size_t MaxApps>
class tMinExCoordinator
{
public:
...
tMinExCoordinator()
{
Applications = reinterpret_cast<tMinExApplication*>(appBuffer);
}
~tMinExCoordinator()
{
for (std::size_t i = 0; i < NumApps; ++i)
Applications[i].~tMinExApplication();
}
void addApp(iMinExApplication* app, const char name[])
{
if (NumApps >= MaxApps)
throw std::length_error("");
new (&appBuffer[NumApps]) tMinExApplication(app, name);
++NumApps;
}
...
void runApps()
{
for (std::size_t i = 0; i < NumApps; ++i)
{
auto& app = Applications[i];
// Frequency check
// ...
app.execute();
}
}
private:
typename std::aligned_storage<sizeof(tMinExApplication), alignof(tMinExApplication)>::type appBuffer[MaxApps];
tMinExApplication* Applications;
std::size_t NumApps = 0;
};
tMinExCoordinator<1> coordinator{};
...
The std::aligned_storage documentation linked above has an example static_vector class that uses a fixed memory buffer, which will be on the stack if the vector is constructed on the stack:
#include <iostream>
#include <type_traits>
#include <string>
template<class T, std::size_t N>
class static_vector
{
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
std::size_t m_size = 0;
public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
{
if( m_size >= N ) // possible error handling
throw std::bad_alloc{};
// construct value in memory of aligned storage
// using inplace operator new
new(&data[m_size]) T(std::forward<Args>(args)...);
++m_size;
}
// Access an object in aligned storage
const T& operator[](std::size_t pos) const
{
// note: needs std::launder as of C++17
return *reinterpret_cast<const T*>(&data[pos]);
}
// Delete objects from aligned storage
~static_vector()
{
for(std::size_t pos = 0; pos < m_size; ++pos) {
// note: needs std::launder as of C++17
reinterpret_cast<T*>(&data[pos])->~T();
}
}
};
You can use that class in your coordinator, with some minor additions to it so it can work with loops, eg:
template<class T, std::size_t N>
class static_vector
{
// properly aligned uninitialized storage for N T's
typename std::aligned_storage<sizeof(T), alignof(T)>::type data[N];
std::size_t m_size = 0;
public:
// Create an object in aligned storage
template<typename ...Args> void emplace_back(Args&&... args)
{
if( m_size >= N ) // possible error handling
throw std::bad_alloc{};
// construct value in memory of aligned storage
// using inplace operator new
new(&data[m_size]) T(std::forward<Args>(args)...);
++m_size;
}
// Access an object in aligned storage
T& operator[](std::size_t pos)
{
// note: needs std::launder as of C++17
return *reinterpret_cast<T*>(&data[pos]);
}
const T& operator[](std::size_t pos) const
{
// note: needs std::launder as of C++17
return *reinterpret_cast<const T*>(&data[pos]);
}
std::size_t size() const { return m_size; }
std::size_t capacity() const { return N; }
// iterator access to objects
T* begin()
{
// note: needs std::launder as of C++17
return reinterpret_cast<T*>(&data[0]);
}
T* end()
{
// note: needs std::launder as of C++17
return reinterpret_cast<T*>(&data[m_size]);
}
const T* cbegin() const
{
// note: needs std::launder as of C++17
return reinterpret_cast<const T*>(&data[0]);
}
const T* cend() const
{
// note: needs std::launder as of C++17
return reinterpret_cast<const T*>(&data[m_size]);
}
// Delete objects from aligned storage
~static_vector()
{
for(std::size_t pos = 0; pos < m_size; ++pos) {
// note: needs std::launder as of C++17
reinterpret_cast<T*>(&data[pos])->~T();
}
}
};
template<std::size_t MaxApps>
class tMinExCoordinator
{
public:
...
void addApp(iMinExApplication* app, const char name[])
{
Applications.emplace_back(app, name);
}
...
void runApps()
{
for (auto& app : Applications)
{
// Frequency check
// ...
app.execute();
}
}
private:
static_vector<tMinExApplication, MaxApps> Applications;
};
I have though of smart pointers, but am unsure if they use heap memory or not.
By default, they rely on new and delete, and thus dynamic memory. Though, you can supply them with pointers to stack memory, if you also supply them with custom deleters that won't free that memory.
Related
I have a few unordered_maps that I use with custom allocators. In this case I use a rudimentary bump allocator that just simply linearly allocates new memory from an existing continiuos block.
But when I try and call reserve on these maps after a while, they throw an access violaton exception at the line within the std list file I'll show below in :
_List_unchecked_const_iterator& operator++() noexcept {
_Ptr = _Ptr->_Next;
return *this;
}
My allocator has enough memory left so I don't think it's because I am running out of memory.
Here's some self contained code that demonstrates it. Copy/paste and run it to see the issue.
#include <stdlib.h>
#include <unordered_map>
#include <vector>
#include <list>
#include <memory>
#define SEQ(type, val) sizeof(type) * val
struct ImpAllocator {
virtual void* Allocate(size_t pSize) = 0;
virtual void Free(void* pPtr) = 0;
};
struct SysAllocator : public ImpAllocator {
void* Allocate(size_t pSize) override {
return malloc(pSize);
}
void Free(void* pPtr) override {
free(pPtr);
}
};
template <class T>
class StdAllocatorWrapper {
public:
std::shared_ptr<ImpAllocator> mInternalAllocator;
using value_type = T;
StdAllocatorWrapper() = default;
StdAllocatorWrapper(std::shared_ptr<ImpAllocator> pInternalAllocator) :
mInternalAllocator(pInternalAllocator)
{}
~StdAllocatorWrapper() = default;
StdAllocatorWrapper(const StdAllocatorWrapper<T>& pOther) = default;
template<class U>
StdAllocatorWrapper(const StdAllocatorWrapper<U>& pOther) {
this->mInternalAllocator = pOther.mInternalAllocator;
}
value_type* allocate(size_t pNumberOfObjects) {
return reinterpret_cast<T*>(mInternalAllocator->Allocate(SEQ(T, pNumberOfObjects)));
}
void deallocate(value_type* pPointer, size_t pNumberOfObjects) {
mInternalAllocator->Free(pPointer);
}
};
template <class T, class U>
bool operator==(StdAllocatorWrapper<T> const& pL, StdAllocatorWrapper<U> const& pR) noexcept {
return pL.mInternalAllocator == pR.mInternalAllocator;
}
template <class T, class U>
bool operator!=(StdAllocatorWrapper<T> const& pL, StdAllocatorWrapper<U> const& pR) noexcept {
return !(pL == pR);
}
template<typename T> using AllocWrapper = StdAllocatorWrapper<T>;
template<typename T, typename K> using Pair = std::pair<const T, K>;
template<typename T, typename K> using PairAllocWrapper = StdAllocatorWrapper<Pair<T, K>>;
template<typename T> using AllocatedVector = std::vector<T, AllocWrapper<T>>;
template<typename T> using AllocatedList = std::list<T, AllocWrapper<T>>;
template<typename T, typename K> using AllocatedUnorderedMap = std::unordered_map<T, K, std::hash<T>, std::equal_to<T>, PairAllocWrapper<T, K>>;
typedef unsigned char* MemBlock;
class BumpAllocator : public ImpAllocator {
private:
std::shared_ptr<ImpAllocator> mInternalAllocator;
size_t mSize;
MemBlock mBlock;
MemBlock mStart;;
size_t mCurrent;
public:
BumpAllocator(size_t pSize, std::shared_ptr<ImpAllocator> pInternalAllocator) :
mInternalAllocator(pInternalAllocator),
mSize(pSize),
mCurrent(0) {
mBlock = reinterpret_cast<MemBlock>(mInternalAllocator->Allocate(pSize));
mStart = mBlock;
}
~BumpAllocator() {
mInternalAllocator->Free(mBlock);
}
void* Allocate(size_t pSize) override {
printf("\n bump allocator wrapper requested: %d", pSize);
if (mCurrent + pSize > mSize) {
return nullptr;
}
MemBlock _return = mBlock + mCurrent;
mCurrent += pSize;
return _return;
}
void Free(void* pFre) override {
}
void Reset() {
mCurrent = 0;
}
};
struct Animation {
};
struct Texture {
};
struct TextureArrayIndex {
//TexturePointer mTexture;
unsigned int mIndex;
std::shared_ptr<Texture> mTexture;
};
struct RenderOrder {
float mDeltaTime;
std::string mAnimationName;
std::shared_ptr<Animation> mAnim;
};
using Textures = AllocatedUnorderedMap<int, TextureArrayIndex>;
using TexturesAllocWrapper = PairAllocWrapper<int, TextureArrayIndex>;
using RenderOrdersVector = AllocatedVector<RenderOrder>;
using RenderOrdersAllocWrapper = AllocWrapper<RenderOrder>;
using RenderBucket = AllocatedUnorderedMap<unsigned int, RenderOrdersVector>;
using RenderBuckets = AllocatedUnorderedMap<std::shared_ptr<Animation>, RenderBucket>;
using RenderBucketAllocWrapper = PairAllocWrapper<unsigned int, RenderOrdersVector>;
using RenderBucketsAllocWrapper = PairAllocWrapper<std::shared_ptr<Animation>, RenderBucket>;
struct Renderer {
std::shared_ptr<BumpAllocator> mInternalAllocator;
Textures mTextureArrayIndexMap;
RenderBuckets mAnimationRenderBuckets;
Renderer(std::shared_ptr<ImpAllocator> pAllocator) :
mInternalAllocator(std::make_shared<BumpAllocator>(60000, pAllocator)),
mTextureArrayIndexMap(Textures(TexturesAllocWrapper(mInternalAllocator))),
mAnimationRenderBuckets(RenderBuckets(RenderBucketsAllocWrapper(mInternalAllocator)))
{}
void Begin() {
mTextureArrayIndexMap = Textures(TexturesAllocWrapper(mInternalAllocator));
mTextureArrayIndexMap.reserve(2);
mAnimationRenderBuckets = RenderBuckets(RenderBucketsAllocWrapper(mInternalAllocator));
mAnimationRenderBuckets.reserve(1000);
}
void Render() {
}
void Flush() {
mInternalAllocator->Reset();
}
};
int main(int argc, char* argv[]) {
Renderer _renderer(std::make_shared<SysAllocator>());
for (int i = 0; i < 1000; i++) {
_renderer.Begin();
_renderer.Flush();
}
}
What's weird is that , the first 2 iterations work fine. But the third one fails... So reusing the memory works for the first 2 Begin--->Flush cycles but then the 3d one fail every time. So odd.
I could not reproduce the error with the code given... But the code does not use std::unordered_map, so the error must come from there.
First issue. In your allocators:
void* SysAllocator::Allocate(size_t pSize) override {
return malloc(pSize); // allocator returns NULL on error.
}
void* BumpAllocator::Allocate(size_t pSize) override {
printf("\n bump allocator wrapper requested: %d", pSize);
if (mCurrent + pSize > mSize) {
return nullptr; // allocator returns NULL on error.
}
MemBlock _return = mBlock + mCurrent;
mCurrent += pSize;
return _return;
}
Your custom allocators return NULL on error, but STL containers expect their allocators to throw a std::bad_alloc exception on error. That's mandatory.
You should change your allocators:
void* SysAllocator::Allocate(size_t pSize) override {
void* p = malloc(pSize);
if (!p)
throw std::bad_alloc();
return p;
}
void* BumpAllocator::Allocate(size_t pSize) override {
printf("\n bump allocator wrapper requested: %d", pSize);
if (mCurrent + pSize > mSize)
throw std::bad_alloc();
MemBlock _return = mBlock + mCurrent;
mCurrent += pSize;
return _return;
}
This will not solve your problem, but the point of error will have moved to the time of allocation.
I think your problem comes from a lack of memory. std::unordered_map uses hash buckets, and these could very likely use more memory than you anticipated. To investigate, set breakpoints on the throw lines in your Allocate() functions to catch the error right when it happens.
Apart from the issue mentioned by Michael Roy, there is another bug here
void Reset() {
mCurrent = 0;
}
This method, which is called indirectly from your main, means that further allocations will reuse already allocated memory. This is the immediate cause of the crash in the code you posted.
If you comment out the line mCurrent = 0; then you do actually run out of memory and you get the null pointer bug explained by Michael Roy in the other answer.
Tested with MSVC (since that seems to be significant).
I figured it out finally. The problem was that inside the Begin method, I am initializing the maps again. This happens after Reset is called. Meaning the memory allocation takes place from the beginning. This would be fine. However, at this time. Both the new maps and the old ones exist at the same time and they overlap fully on the same memory. This also would be fine but then when the new map is assigned to the old one, the destructor of the old one is called and it frees the memory that is being used by the newly instantiated maps.
I fixed the issue by making the maps pointers ( std::unordered_map* ) . So now my map "reinstantiation" is as follows mTextureArrayIndexMap = new Textures(...) . So the destructor of the old map is not called. And this is perfectly fine since the whole thing is being allocated on a bump memory that is reset.
The code runs smoothly now with no issues!
There are situations in which I need to pass a char* buffer back and forth. My idea is to create an object which can hold the object that owns the data, but also expose the data as char* for someone to read. Since this object holds the owner, there are no memory leaks because the owner is destructed with the object when it's no longer necessary.
I came with the implementation below, in which we have a segfault that I explain why it happens. In fact it's something that I know how to fix but it's something that my class kinda lured me into doing. So I consider what I've done to be not good and maybe there's a better way of doing this in C++ that is safer.
Please take a look at my class that holds the buffer owner and also holds the raw pointer to that buffer. I used GenericObjectHolder to be something that holds the owner for me, without my Buffer class being parametrized by this owner.
#include <iostream>
#include <string>
#include <memory>
#include <queue>
//The library:
class GenericObjectHolder
{
public:
GenericObjectHolder()
{
}
virtual ~GenericObjectHolder() {
};
};
template <class T, class Holder = GenericObjectHolder>
class Buffer final
{
public:
//Ownership WILL be passed to this object
static Buffer fromOwned(T rawBuffer, size_t size)
{
return Buffer(std::make_unique<T>(rawBuffer), size);
}
//Creates a buffer from an object that holds the buffer
//ownership and saves the object too so it's only destructed
//when this buffer itself is destructed
static Buffer fromObject(T rawBuffer, size_t size, Holder *holder)
{
return Buffer(rawBuffer, std::make_unique<T>(rawBuffer), size, holder);
}
//Allocates a new buffer with a size
static Buffer allocate(size_t size)
{
return Buffer(std::make_unique<T>(new T[size]), size);
}
~Buffer()
{
if (_holder)
delete _holder;
}
virtual T data()
{
return _rawBuffer;
}
virtual size_t size() const
{
return _size;
}
Buffer(T rawBuffer, std::unique_ptr<T> buffer, size_t size)
{
_rawBuffer = rawBuffer;
_buffer = std::move(buffer);
_size = size;
}
Buffer(T rawBuffer, std::unique_ptr<T> buffer, size_t size, Holder *holder)
{
_rawBuffer = rawBuffer;
_buffer = std::move(buffer);
_size = size;
_holder = holder;
}
Buffer(const Buffer &other)
: _size(other._size),
_holder(other._holder),
_buffer(std::make_unique<T>(*other._buffer))
{
}
private:
Holder *_holder;
T _rawBuffer;
std::unique_ptr<T> _buffer;
size_t _size = 0;
};
//Usage:
template <class T>
class MyHolder : public GenericObjectHolder
{
public:
MyHolder(T t) : t(t)
{
}
~MyHolder()
{
}
private:
T t;
};
int main()
{
std::queue<Buffer<const char*, MyHolder<std::string>>> queue;
std::cout << "begin" << std::endl;
{
//This string is going to be deleted, but `MyHolder` will still hold
//its buffer
std::string s("hello");
auto h = new MyHolder<std::string>(s);
auto b = Buffer<const char*, MyHolder<std::string>>::fromObject(s.c_str(),s.size(), h);
queue.emplace(b);
}
{
auto b = queue.front();
//We try to print the buffer from a deleted string, segfault
printf(b.data());
printf("\n");
}
std::cout << "end" << std::endl;
}
As you see, the s string is copied inside the object holder but gets destructed right after it. So when I try to access the raw buffer that buffer owns I get a segfault.
Of course I could simply copy the buffer from the s string into a new buffer inside my object, but It'd be inefficient.
Maybe there's a better way of doing such thing or maybe there's even something ready in C++ that does what I need.
PS: string is just an example. In pratice I could be dealing with any type of object that owns a char* buffer.
Live example: https://repl.it/repls/IncredibleHomelySdk
Your core problem is that you want your Holder to be moveable. But when the Owner object moves, the buffer object might also move. That will invalidate your pointer. You can avoid that by putting the owner in a fixed heap location via unique_ptr:
#include <string>
#include <memory>
#include <queue>
#include <functional>
template <class B, class Owner>
class Buffer
{
public:
Buffer(std::unique_ptr<Owner>&& owner, B buf, size_t size) :
_owner(std::move(owner)), _buf(std::move(buf)), _size(size)
{}
B data() { return _buf; }
size_t size() { return _size; }
private:
std::unique_ptr<Owner> _owner;
B _buf;
size_t _size;
};
//Allocates a new buffer with a size
template<typename T>
Buffer<T*, T[]> alloc_buffer(size_t size) {
auto buf = std::make_unique<T[]>(size);
return {std::move(buf), buf.get(), size};
}
Here's a repl link: https://repl.it/repls/TemporalFreshApi
If you want to have a type-erased Buffer, you can do that like this:
template <class B>
class Buffer
{
public:
virtual ~Buffer() = default;
B* data() { return _buf; }
size_t size() { return _size; }
protected:
Buffer(B* buf, size_t size) :
_buf(buf), _size(size) {};
B* _buf;
size_t _size;
};
template <class B, class Owner>
class BufferImpl : public Buffer<B>
{
public:
BufferImpl(std::unique_ptr<Owner>&& owner, B* buf, size_t size) :
Buffer<B>(buf, size), _owner(std::move(owner))
{}
private:
std::unique_ptr<Owner> _owner;
};
//Allocates a new buffer with a size
template<typename T>
std::unique_ptr<Buffer<T>> alloc_buffer(size_t size) {
auto buf = std::make_unique<T[]>(size);
return std::make_unique<BufferImpl<T, T[]>>(std::move(buf), buf.get(), size);
}
Again, repl link: https://repl.it/repls/YouthfulBoringSoftware#main.cpp
You wrote:
There are situations in which I need to pass a char* buffer back and
forth.
and
So I consider what I've done to be not good and maybe there's a better
way of doing this in C++ that is safer.
It's not exactly clear what you are aiming at, but when I have this need i will sometimes use std::vector<char> - a std::vector (and std::string) is a just that: a managed buffer. Calling data() on vector will give you a raw pointer to the buffer to pass on to legacy interfaces etc. or for whatever reason you just need a buffer that you manage yourself. Hint: use resize() or constructor to allocate the buffer.
So you see, there's no need to store the internal pointer of std::string in your example. Instead just call data() on a need basis.
It seems like you are concerned about copies and efficiency. If you use objects that support move semantics and you use the emplace family of functions there shouldn't be any copy-ing going on at least in c++17. All/most containers supports moving as well.
The class std::unique_ptr is already a "buffer holder" that "guarantee delete", no string copies, no dangling references and no seg faults:
#include <iostream>
#include <queue>
#include <memory>
int main()
{
std::queue<std::unique_ptr<std::string>> queue;
std::cout << "begin" << std::endl;
{
auto h = std::make_unique<std::string>("Hello");
queue.emplace( std::move(h) ); // move into the queue without copy
}
{
auto b = std::move(queue.front()); // move out from queue without copy
std::cout << *b << std::endl;
} // when b goes out of scope it delete the string
std::cout << "end" << std::endl;
}
https://godbolt.org/z/neP838
I am having trouble changing the memory resource of a map in my custom container.
It seems I can neither change the allocator of the map, nor create a new map with the custom memory resource.
#include <iostream>
#include <memory_resource>
#include <map>
using Map = std::pmr::map<const std::string, int>;
class custom_resource : public std::pmr::memory_resource {
public:
void* do_allocate(std::size_t bytes, std::size_t alignment) override {
std::cout << "Using custom resource" << std::endl;
return std::pmr::get_default_resource()->allocate(bytes, alignment);
}
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return false;
}
};
struct MyContainer {
void Reset(std::pmr::memory_resource &resource)
{
map_ = Map{&resource};
}
void AddItem(std::string k) {
map_[k] = 1;
}
std::pmr::memory_resource* default_resource = std::pmr::get_default_resource();
// custom_resource custom{}; // uncomment this and next line to see it work
// Map map_{&custom};
Map map_{default_resource};
};
int main() {
MyContainer container{};
container.AddItem("a"); // no output expected
custom_resource custom{};
container.Reset(custom);
container.AddItem("b"); // output expected
return 0;
}
https://godbolt.org/z/aVMuw6
How can I reset the container at runtime such that the custom memory resource is used to allocate items in the map?
You cannot change a container's Allocator after it has been constructed. And even polymorphic_allocator's memory resource cannot be changed after it has been constructed. So what you want isn't really doable; you are expected to know what memory resource your container will use at the time of its construction and that is the allocator+resource that it will use until its destruction.
There are circumstances that would allow you to copy/move/swap a new allocator into a container, but polymorphic_allocator disallows all of them.
I have a scenario where I have multiple operations represented in the following way:
struct Op {
virtual void Run() = 0;
};
struct FooOp : public Op {
const std::vector<char> v;
const std::string s;
FooOp(const std::vector<char> &v, const std::string &s) : v(v), s(s) {}
void Run() { std::cout << "FooOp::Run" << '\n'; }
};
// (...)
My application works in several passes. In each pass, I want to create many of these operations and at the end of the pass I can discard them all at the same time. So I would like to preallocate some chunk of memory for these operations and allocate new operations from this memory. I came up with the following code:
class FooPool {
public:
FooPool(int size) {
foo_pool = new char[size * sizeof(FooOp)]; // what about FooOp alignment?
cur = 0;
}
~FooPool() { delete foo_pool; }
FooOp *New(const std::vector<char> &v, const std::string &s) {
return new (reinterpret_cast<FooOp*>(foo_pool) + cur) FooOp(v,s);
}
void Release() {
for (int i = 0; i < cur; ++i) {
(reinterpret_cast<FooOp*>(foo_pool)+i)->~FooOp();
}
cur = 0;
}
private:
char *foo_pool;
int cur;
};
This seems to work, but I'm pretty sure I need to take care somehow of the alignment of FooOp. Moreover, I'm not even sure this approach is viable since the operations are not PODs.
Is my approach flawed? (most likely)
What's a better way of doing this?
Is there a way to reclaim the existing memory using unique_ptrs?
Thanks!
I think this code will have similar performance characteristics without requiring you to mess around with placement new and aligned storage:
class FooPool {
public:
FooPool(int size) {
pool.reserve(size);
}
FooOp* New(const std::vector<char>& v, const std::string& s) {
pool.emplace_back(v, s); // in c++17: return pool.emplace_back etc. etc.
return &pool.back();
}
void Release() {
pool.clear();
}
private:
std::vector<FooOp> pool;
}
The key idea here being that your FooPool is essentially doing what std::vector does.
I have a class idx_aware that goes into a container container, which wraps around a std::vector. When the class is added to container, container sets a pointer to itself in idx_aware, as well as the index of idx_aware in its internal memory storage.
The index is not going to change until the container is destroyed or idx_aware is removed; idx_aware needs to know about its container and its index, because it has some methods that require both to work.
Now this introduces the following problem: when I get a non-const reference to an idx_aware class contained in container, I could assign to it another idx_aware class, which could have a different index. The intention would be assigning all the fields and keeping the index as it is.
#include <vector>
#include <limits>
#include <iostream>
class container;
// Stores a std::size_t field, which can be set only by subclasses.
class with_idx {
std::size_t _i;
public:
with_idx() : _i(std::numeric_limits<std::size_t>::max()) {}
operator std::size_t() const { return _i; }
protected:
void set_idx(std::size_t i) { _i = i; }
};
// Knows its index and its container
class idx_aware : public with_idx {
container const *_container;
int _some_field1;
float _some_field2;
public:
void foo() {
// Do stuff using _container and _i
}
private:
friend class container;
};
// Wraps around a std::vector
class container {
std::vector<idx_aware> _data;
public:
idx_aware &operator[](std::size_t idx) {
// Need non-const access to call foo
return _data[idx];
}
idx_aware const &operator[](std::size_t idx) const {
return _data[idx];
}
std::size_t add(idx_aware const &item) {
// Here it could potentially reuse a freed position
std::size_t free_slot = _data.size();
// Ensure _data is big enough to contain free_slot
if (_data.size() <= free_slot) {
_data.resize(free_slot + 1);
}
// Assign
_data[free_slot] = item;
_data[free_slot].set_idx(free_slot);
_data[free_slot]._container = this;
return free_slot;
}
};
int main() {
container c;
idx_aware an_item;
std::size_t i = c.add(an_item);
std::cout << c[i] << std::endl; // Prints 0
idx_aware another_item; // Created from somewhere else
// I want to set all the data in idx_aware, but the
// index should stay the same!
c[i] = another_item;
std::cout << c[i] << std::endl; // Prints numeric_limits<size_t>::max()
// Now container[i] is broken because it doesn't know anymore its index.
return 0;
}
One possible workaround would be to change with_idx in such a way that when set_idx is called, a flag is set that prevents assignment and copy operator to overwrite the _i property, like this:
class with_idx {
std::size_t _i;
bool _readonly;
public:
with_idx() : _i(std::numeric_limits<std::size_t>::max()), _readonly(false) {}
with_idx(with_idx const &other) : _i(other._i), _readonly(false) {}
with_idx &operator=(with_idx const &other) {
if (!_readonly) {
_i = other._i;
}
return *this;
}
operator std::size_t() const { return _i; }
protected:
void set_idx(std::size_t i) {
_i = i;
if (i != std::numeric_limits<std::size_t>::max()) {
// This has been set by someone with the right to do so,
// prevent overwriting
_readonly = true;
} else {
// Removed from the container, allow overwriting
_readonly = false;
}
}
};
This would have the consequence of returning, after assignment, a reference to an idx_aware class with unchanged index.
idx_aware ¬_in_container1 = /* ... */;
idx_aware ¬_in_container2 = /* ... */;
idx_aware &in_container = /* ... */;
not_in_container1 = in_container = not_in_container2;
// std::size_t(not_in_container_1) != std::size_t(not_in_container_2)
Is there a design pattern that can model this situation in a better way? My searches were not successful.
Are there other unwanted consequences of overriding the assignment operator in this way? The limitation I pointed out in the previous example does not look too "bad".
Is there an easier solution? I thought about writing some proxy object to replace the idx_aware & return type of operator[].
Experience tells that when C++ does not do what you intend, you are likely to be misusing OOP...
Robert's comment suggested me this solution.
Why would the contained object know about its container? To be able to perform actions such as foo and provide shorthand methods that otherwise would require to have access to the container.
Let's take this functionality away from the contained object; the contained object is just data payload. Instead, let's make operator[] return not the contained object, but some sort of iterator, a wrapper around the contained object, which knows the container and the index, and once dereferenced returns the actual contained object.
class was_idx_aware {
int _some_field1;
float _some_field2;
};
class container {
std::vector<idx_aware> _data;
public:
class idx_aware_wrapper {
container const *_container;
std::size_t _idx;
public:
idx_aware_wrapper(container const &c, std::size_t i)
: _container(&c)
, _idx(i)
{}
was_idx_aware const &operator*() const {
return _container->_data[_idx];
}
was_idx_aware &operator*() {
return _container->_data[_idx];
}
void foo() {
// Do stuff using _container and _idx.
}
};
idx_aware_wrapper operator[](std::size_t i) {
return idx_aware_wrapper(*this, i);
}
/* .... */
};
This allows quick access to any data in was_idx_aware, and the wrapper class can be augmented with all the methods that require interaction with the container. No need to store and keep indices up to date or override assignment operators.