简体   繁体   中英

C++: safely initialize a class with pointer to a large static array

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

}

But what if my compiler is a little old and does not treat the address of a variable as a constant expression?

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

}

https://godbolt.org/z/6m2BM8

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM