C ++:使用指向大型静态数组的指针安全地初始化类

时间:2018-10-11 03:20:40

标签: c++

我有一个低层的嵌入式应用程序,其中有一些相对较大的const,全局,静态数组(查找表等)。编译器(或链接器)将它们存储在闪存而不是RAM中,因为它们是const。

现在,我有一个需要使用一个这样的数组初始化的类。它将在类对象的整个生命周期内使用该数组中的数据。

我的问题是:如何安全地将指向此全局静态数组的指针传递给对象,同时防止错误地传递寿命短而不是静态数组的数组?

例如,请考虑无法保护用户免受不正确初始化的天真的实现:

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
}

如何防止示例中的第二个实例化?也许以某种方式通过constexpr或模板的某些巧妙用法...?

注意:

  • 我意识到这里的问题是我的课程不符合RAII。但是,在上述约束下(使用闪存中的大型静态数组),我看不到如何使其符合RAII。

  • 将数据从静态数组复制到对象中的本地数据成员是不可能的-单个数组实际上可能比我的整个RAM大,大小仅为几十kB。

  • 我将拥有该类的多个实例,多个静态数据表,并且可以使用同一静态数据表初始化该类的多个实例。

您对在这里加强安全性的设计模式有何想法?

谢谢!

1 个答案:

答案 0 :(得分:4)

变量的地址是一个常量表达式。这意味着我们可以将表的地址用作模板参数。

通过这种方式,我们可以为存在的每个插值表构建一个特定的模板类,而没有其他的。

这消除了创建指向瞬态表的插值器的可能性。

由于不需要维护指向数据的指针,它还具有需要较少存储空间的优点。

示例:

#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);    
}

但是,如果在某些情况下我们想要有条件地对一个或另一个表进行插值呢?

在这种情况下,我们可以使InterpolatorImpl多态,这是基于一个共同的基础而来的。然后,我们可以为公用库提供一种基于通过私有虚拟函数获取的表详细信息进行插值的方法。

#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

}

但是如果我的编译器有点旧,并且不将变量的地址视为常量表达式怎么办?

然后我们需要为每个插值器进行一些手动滚动:

#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