I have a low-level embedded application, where I have some relatively large const, global, static arrays (lookup tables and such). The compiler (or linker) stores them in Flash memory rather than in RAM, since they are const.
Now, I have a class that needs to be initialized with one such array. It will use the data from that array throughout the lifetime of the class object.
My question is: how can I safely pass a pointer to this global, static array to the object, while preventing mistakenly passing an array with a short lifetime rather than a static one?
For example, consider the naive implementation that doesn't protect from incorrect initialization:
class Interpolator
{
public:
Interpolator(const float table[], int size);
float interpolate(float x); // uses 'table' data member
private:
const float* table;
int size;
};
Interpolator::Interpolator(const float table[], int size) :
table(table), size(size)
{
}
const float table1[] = {1.0, 2.0, 42.0 /* a few thousand more */ };
void main()
{
Interpolator interpolator1(table1, sizeof(table1) / sizeof(float));
float x = interpolator1.interpolate(17.0); // OK
float* table2 = new float[1024];
// ... calculate and fill in values in table2 ...
Interpolator interpolator2(table2, 1024); // how to prevent this usage?
delete[] table2; // incorrectly assume the object created a copy for itself and the delete is safe...
float y = interpolator2.interpolate(17.0); // ERROR, undefined behavior
}
How do I prevent the second instantiation in the example? perhaps through constexpr somehow, or some clever usage of templates...?
Notes:
I realize that the problem here is that my class doesn't conform to RAII. However, under the constraints explained above (use a large static array from Flash memory), I don't see how I can make it conform to RAII.
Copying the data from the static array to a local data member in the object is out of the question - a single array may literally be larger than my whole RAM, which is only tens of kB in size.
I will have multiple instances of the class, multiple static data tables, and several instances of the class may be initialized with the same static data table.
Any idea for a design pattern that enforces safety here?
thanks!
The address of a variable is a constant expression. This means we can use the address of the table as a template argument.
In this way we can build a specific template class for each interpolation table that exists, and no others.
This removes the possibility of creating an interpolator which points to a transient table.
It also has the advantage of requiring less storage since it does not need to maintain pointers to the data.
example:
#include <cstddef>
template<const float* const Table, std::size_t Size>
struct InterpolatorImpl
{
public:
float interpolate(float x)
{
// use Table and Size here as constant expressions
// or write in terms of begin() and end()
return 0;
}
constexpr std::size_t size() const
{
return Size;
}
constexpr const float* begin() const
{
return Table;
}
constexpr const float* end() const
{
return begin() + size();
}
};
const float table1[] = {1.0, 2.0, 42.0 /* a few thousand more */ };
using Interpolator1 = InterpolatorImpl<table1, sizeof(table1) / sizeof(float)>;
const float table2[] = {1.0, 3.0, 5.0 /* a few thousand more */ };
using Interpolator2 = InterpolatorImpl<table2, sizeof(table2) / sizeof(float)>;
int main()
{
Interpolator1 interpolator1;
float x = interpolator1.interpolate(17.0); // OK
float y = Interpolator2().interpolate(21);
}
But what if there were cases where we wanted to conditionally interpolate against one or another table?
In this case we could make the InterpolatorImpl polymorphic, deriving from a common base. We could then provide the common base with a means of performing interpolation based on table details acquired through a private virtual function.
#include <cstddef>
struct Interpolator
{
float interpolate(float x) const
{
return interpolate(getDetails(), x);
}
protected:
struct Details
{
const float* first;
std::size_t length;
};
private:
virtual Details getDetails() const = 0;
static float interpolate(Details details, float x)
{
// do interpolation here
auto begin = details.first;
auto size = details.length;
// ...
return 0;
}
};
template<const float* const Table, std::size_t Size>
struct InterpolatorImpl : Interpolator
{
public:
constexpr std::size_t size() const
{
return Size;
}
constexpr const float* begin() const
{
return Table;
}
constexpr const float* end() const
{
return begin() + size();
}
private:
virtual Details getDetails() const override
{
return { Table, Size };
}
friend auto poly(InterpolatorImpl const& i) -> Interpolator const&
{
return i;
}
};
const float table1[] = {1.0, 2.0, 42.0 /* a few thousand more */ };
using Interpolator1 = InterpolatorImpl<table1, sizeof(table1) / sizeof(float)>;
const float table2[] = {1.0, 3.0, 5.0 /* a few thousand more */ };
using Interpolator2 = InterpolatorImpl<table2, sizeof(table2) / sizeof(float)>;
float doInterpolation(Interpolator const& interp, float x)
{
return interp.interpolate(x);
}
bool choice();
int main()
{
Interpolator1 interpolator1;
Interpolator2 interpolator2;
float x = doInterpolation(choice() ? poly(interpolator1) : poly(interpolator2) , 17.0); // OK
}
Then we need a little hand-rolling for each interpolator:
#include <cstddef>
#include <type_traits>
struct Interpolator
{
float interpolate(float x) const
{
return interpolate(getDetails(), x);
}
protected:
struct Details
{
const float* first;
std::size_t length;
};
private:
virtual Details getDetails() const = 0;
static float interpolate(Details details, float x)
{
// do interpolation here
auto begin = details.first;
auto size = details.length;
// ...
return 0;
}
friend Interpolator const& poly(Interpolator const& self) { return self; }
};
const float table1[] = {1.0, 2.0, 42.0 /* a few thousand more */ };
struct Interpolator1 : Interpolator
{
virtual Details getDetails() const override
{
return {
table1,
std::extent<decltype(table1)>::value
};
}
};
const float table2[] = {1.0, 3.0, 5.0 /* a few thousand more */ };
struct Interpolator2 : Interpolator
{
virtual Details getDetails() const override
{
return {
table2,
std::extent<decltype(table2)>::value
};
}
};
float doInterpolation(Interpolator const& interp, float x)
{
return interp.interpolate(x);
}
bool choice();
int main()
{
Interpolator1 interpolator1;
Interpolator2 interpolator2;
float x = doInterpolation(choice() ? poly(interpolator1) : poly(interpolator2) , 17.0); // OK
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.