9

首先让我说我正在为微控制器创建软件,因此 RAM 的使用很重要,将大块 const 数据放入非易失性(闪存)存储器是有意义的。

我想要实现的是找到一种在 C++ 中创建“拆分”对象的好方法。作为一个例子,我们假设有一个字节的数据(读/写)和一个多字节的“收据”来访问它。假设“receipt”是一个长字符串,它是一个文件名,它指向的媒体很慢,所以在内存中缓冲单个字节是有意义的,而不是在每个请求时实际读取它。

class Data
{
    uint8_t byte;
    bool valid;
    const char filename[128];
    uint8_t read()
    {
        if (!valid)
            performReallySlowRead(filename, &byte);
        valid = true;
        return byte;
    };
    void write(uint8_t new_value)
    {
        byte = new_value;
        performRealWriteToMedia(filename, byte);
        valid = true;
    };
}

这种方法的明显问题是整个 130 字节最终都在 RAM 中,而其中只有两个需要更改。所以我想出了一个拆分对象的想法:

class DataNonConst
{
    uint8_t byte;
    bool valid;
}

class DataConst
{
    const char filename[128];
    DataNonConst * const data;
}

static DataNonConst storage;
const DataConst holder("some_long_name_here", &storage);

现在唯一的问题是,如果我想要数百个这样的拆分对象,那么创建它们的过程(因此创建两个对象并链接第二个到第一个)会变得非常无聊和有问题......

所以问题是 - 有没有一些好方法让它更容易使用,最好是聪明的 C++ 技巧或者一些模板魔法?也就是说 - 如何创建两个链接在一起的对象,用一个语句,最好是隐藏一个对象?我不认为宏解决方案在这里是可能的,因为没有简单的方法来自动创建存储对象的名称......对象需要是相同的类型,因为我需要在其他地方嵌入指向这些对象的指针(一个函数处理编写它们,另一个只关心读取)......我想到的所有解决方案都需要使用模板的虚拟接口(所以你通过 vtable 指针使对象变大并且可能得到一个奖励模板-bloat) 或导致巨大的模板膨胀...

编辑:

实际上,整个问题的一部分可以简化为一个更简单的问题——有没有办法将匿名变量“绑定”到 C++ 中的成员字段?某事喜欢:

const ConstData holder("...", NonConstData()); // this object HAS TO be ROMable

在上面的“一厢情愿”中,持有者是 ROM 中的 const 对象,它具有指向在 RAM 中“某处”创建的匿名对象 NonConstData 的指针/引用/任何内容。或者:

std:pair<const ConstData &, NonConstData &> holder(ConstData(), NonConstData());

任何允许我不手动创建两个对象并将一个对象绑定到另一个对象的东西。

4

6 回答 6

2

有一些限制,您可以利用模板来创建由每个 ROMable DataConst 对象引用的 DataNonConst 的静态实例。

限制:

  • 代码使用__LINE__宏,因此每行只能有一个 DataConst 声明。或者,如果__COUNT__ 宏可用,则可以使用它来代替__LINE__每行允许多个声明。

  • DataNonConst对象总是零初始化。

代码:

struct DataNonConst
{
    uint8_t byte;
    bool valid;
};

struct DataConst
{
    const char filename[128];
    DataNonConst * const data;
};

namespace {

template <long n>
struct DataNonConstHolder
{
    static DataNonConst data;
};

template <long n> DataNonConst DataNonConstHolder<n>::data;

}

// If __COUNT__ macro is supported, use it instead of __LINE__
#define Initializer(s) { s, &DataNonConstHolder<__LINE__>::data }

const DataConst dc1 = Initializer("filename1");
const DataConst dc2 = Initializer("filename2");
于 2013-02-13T20:20:24.707 回答
2

您可以用整数替换指针,并为所有对象创建一个静态数组DataNonConst,如下所示:

class DataNonConst {
    uint8_t byte;
    bool valid;
};

static DataNonConst storages[MAX_DATA];

class DataConst {
    const char filename[128];
    const int dataIndex;
    DataNonConst *data() {
        return &storages[dataIndex];
    }
};

const DataConst holderOne("first_long_name_here", 0);
const DataConst holderTwo("second_long_name_here", 1);
const DataConst holderThree("third_long_name_here", 2);

这种方法受到享元模式的启发,尽管很明显它在这里用于不同的目的。

一个明显的缺点是您需要手动计算条目以避免重复。但是,只有一个storages对象,因此没有其他要创建的对象。

于 2013-02-12T18:04:16.910 回答
2

据我所知,这里唯一的问题是您的“句柄”对象有一个char[]成员。我敢打赌,如果您将其替换为const char*,那将解决您的问题,因为字符串文字可能无论如何都在非易失性内存中。

class Data
{
    const char* filename;
    uint8_t byte;
    bool valid;
    Data(const char* filename_) : filename(filename_), valid(false) {}
    uint8_t read()
    {
        if (!valid)
            performReallySlowRead(filename, &byte);
        valid = true;
        return byte;
    };
    void write(uint8_t new_value)
    {
        byte = new_value;
        performRealWriteToMedia(filename, byte);
        valid = true;
    };
};

Data holder("some_long_name_here"); //name is in the non-volatile memory anyway

char buffer[128] = "\var\bin\omg";
Data holder2(buffer); //runtime names work fine, but have to manage the storage yourself.

或者,如果你想变得狡猾,你可以把它做成一个模板,甚至节省这么多的内存(代价是稍微有点混乱。这有一个额外的好处,就是能够轻松添加static成员,这也将在非易失性内存,但不按文件名共享。这并不明显,但这可以用于运行时名称未知的文件,但我不建议这样做。

template<const char* filename>
class Data
{
    uint8_t byte;
    bool valid;
    uint8_t read()
    {
        if (!valid)
            performReallySlowRead(filename, &byte);
        valid = true;
        return byte;
    };
    void write(uint8_t new_value)
    {
        byte = new_value;
        performRealWriteToMedia(filename, byte);
        valid = true;
    };
};
Data<"some_long_name_here"> holder;

extern char buffer[128] = "\var\bin\omg";
A<buffer> holder2; //this always refers to the filename currently in buffer

但是,如果您真的想要一个拆分对象,那么您所拥有的拆分建议确实是我能想到的最佳答案。

于 2013-02-12T18:43:14.563 回答
0

如果您愿意拥有最大数量的对象,则可以使用索引作为隐式指针:

class Data;
Data *dataArray;
char **filenameArray;

class Data {
  uint8_t byte;
  bool valid;
  char*& filename() {
    int index = this - dataArray;
    return filenameArray[index];
  }
 public:
  byte read() {
    if (!valid)
      performReallySlowRead(filename(), &byte);
    valid = true;
    return byte;
  }
  init(char* fn) {
    filename() = allocateInFlash(strlen(fn));
    strdup(filename(), fn);
    valid = FALSE;
  }
};

// Somewhere before things get run
dataArray = allocateInRam(sizeof(Data)*nDatas);
filenameArray = allocateInFlash(sizeof(char*)*nDatas);

这意味着您不需要任何开销,也不需要触摸闪存来从 ram 读取。您也不再使用标准内存管理,这可能是一个加号或减号。

于 2013-02-12T19:54:30.287 回答
0

只需声明const成员static const

static在任何情况下,成员都与对象实例数据分开存储。您的编译器可能会自动将static const这些成员放在 ROM 中,但如果没有,可能会有编译器指令会强制这样做。

于 2013-02-12T18:30:24.027 回答
0

如果你愿意有一些纯粹的闪存开销,你可以这样做:

class Data;
std::map<Data*,string, less<Data*>, FlashAllocator<pair<Data*,string>> fns;
class Data {
  uint8_t byte;
  bool valid;
 public:
  byte read(){
    if (!valid)
      performReallySlowRead(fns[this],&byte);
    valid = true;
    return byte;
  }
  Data(string fn) {
    fns[this] = fn;
  }
  ~Data() {
    fns.erase(this);
  }
};

地图有不小的开销,但它都在闪存中。

于 2013-02-12T20:00:45.133 回答