Reputation: 510
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:
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.
Upvotes: 0
Views: 59
Reputation: 597941
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.
Upvotes: 2