在编译时以增量方式构建变量指针的向量

Incrementally build vector of variable pointers at compile time

本文关键字:变量 构建 指针 向量 方式 编译      更新时间:2023-10-16

我正在尝试解决一个问题,即我有许多分散的变量声明(在单个编译单元中),并且我想在编译时构建它们的地址向量(如果不可能作为节省空间的向量,其他数据结构也可以工作,例如链表)。

static type var1;
static type var2;
static type var3;
// ...
for (type *i : varlist)
do something with each varX

作为一个扩展的示例和基本原理,假设我有一种脚本语言,并且我有一种方法可以通过获取句柄来C++修改脚本变量,例如scriptvar *。可以通过调用具有名称的get_var函数来找到这些句柄:

scriptvar *var1 = get_var ("namespace::var1");
scriptvar *var2 = get_var ("namespace::var2");

这很慢,所以我希望在全局变量中缓存scriptvar *值。不幸的是,我只能在初始化脚本语言后调用get_var,这可能会在程序后期发生,所以我不能只在初始值设定项表达式中使用get_var,而必须延迟调用它。

如果我能编写一个scriptvarwrapper类或使用其他一些方法,允许我在任何时候声明这些全局句柄变量,并在编译时构建这些 vairable 的空间高效向量,以便稍后访问,那就太好了:

struct scriptvarwrapper
{
scriptvar *handle;
const char *name;
/ ...
};
static scriptvarwrapper var1 ("namespace::var1");
static scriptvarwrapper var2 ("namespace::var2");
static scriptvarwrapper var3 ("namespace::var3");
void init_vars ()
{
for (scriptvarwrapper *i : somecontainer)
i->handle = get_var (i->name);
}

如果这个容器最终只是内存中一个类似数组/矢量的数据结构,由指向这些变量的指针组成,那将是理想的。显然,目标是在编译时构建它,因此将某些内容放入构造函数中的std::vector的解决方案不会解决问题。

更新:

澄清这个问题 - 我想要的是一个解决方案,它在编译时自动编译所有这些声明变量的数组或列表,而无需我单独列出它们,也没有构造函数在运行时动态构建列表,大概是通过一些很酷和漂亮的元编程方法,例如 constexpr 函数以某种方式将所有这些 vairable 链接在一个列表中,或者, 最好是只生成指向内存中所有 scriptvarwrapper 对象的指针数组,这些对象以特殊值或已知大小终止。

具体来说,必须手动将 scriptvarwrapper 对象放入静态数组中是不行的,将它们放入构造函数中的 std::vector 也是不够的。

这样做的基本原理是可维护性 - 如果我在我的程序中的任何位置添加一个变量,我不想再次单独列出它,因为这很容易忘记 - 和效率 - 我不想在运行时构建动态数据结构,实际上是编译时已知的常量。

对某种数组的偏好是由于效率 - 在编译时为每个这样的对象产生 100 字节结构的解决方案当然不是很有用,同样会分配一个非常大的数组只是为了将来扩展。

更新 2:

为了进一步澄清,存储的确切语法、布局和类型并不那么重要。重要的是,我可以有许多独立的和分散的文件变量声明,它们的地址会自动且无需手动枚举地放入某种只读容器中,该容器可以以某种方式在运行时迭代以查找所有这些声明。

目标是有效地找到所有这些变量,而我不可能忘记单独列出一个变量,也不必在运行时动态构建数据结构,因为信息在编译时都是已知的,C++是一种具有编译时元编程的很酷的语言。唉,我不知道如何或是否可能。

更新 3:

很抱歉所有这些更新,我正在学习表达这个问题是多么困难。下面是一个示例,说明情况可能如下所示:

static scriptvarwrapper var1 ("name");
ADD_LIST (var1); // magic macro
static scriptvarwrapper var2 ("name");
ADD_LIST (var2);

这里的关键是,尽管我必须列出每个变量,甚至可能使用一个丑陋的宏,但很难忽略或忘记列出变量,因为ADD_LIST直接位于声明的位置 - 请记住,声明可能分散在长文件中,甚至在某些包含文件中, 所以我正在寻找一种解决方案,让您很难忘记在我的列表中包含声明。

因此,理想情况下,构造函数或仅声明scriptvarwrapper的行为将确保其列出,因此不能忽视它。将所有内容放在构造函数std::vector中的解决方案将起作用,但由于运行时开销而感觉很丑陋。

作为我的老 C 手,我考虑使用 GCC 扩展将它们放入它们自己的 ELF 部分,就像构造函数本身在 ELF 系统上工作一样 - 它们通过指针收集到它们自己的部分,并且所有这些部分在链接时连接起来,末尾有一个特殊的对象文件,提供哨兵结束值。

我不完全确定我是否理解你的问题,但为什么不简单地使用经典的旧数组:

static int var1;
static int var2;
static int var3;
static int* vars[] = { &var1, &var2, &var3, nullptr };
for(size_t i = 0; vars[i]; ++i)
std::cout << *vars[i] << std::endl;

这应该适用于所有数据类型,并且保证在编译时发生。

以下版本也是在编译时构建的(至少在 Visual C++ 2017 中):

static const auto vararr = std::array<int*, 3>{ &var1, &var2, &var3 };

也可以为你做同样的事情:

struct scriptvarwrapper
{
scriptvar *handle;
const char *name;
};
static scriptvarwrapper vars[] = {
{nullptr, "var1"},
{nullptr, "var2"},
{nullptr, nullptr}
};
void init_vars()
{
for (size_t i = 0; vars[i].name; ++i)
vars[i].handle = get_var(vars[i].name);
}

在C++脚本变量 'var1' 可以通过 vars[0].handle 访问,而 'var2' 在 vars[1].handle 中。

也许您更喜欢以下解决方案:

struct scriptvarwrapper
{
scriptvar **handle;
const char *name;
};
static scriptvar *var1 = nullptr;
static scriptvar *var2 = nullptr;
static scriptvarwrapper vars[] = {
{ &var1, "var1"},
{ &var2, "var2"},
{nullptr, nullptr}
};
void init_vars()
{
for (size_t i = 0; vars[i].name; ++i)
*vars[i].handle = get_var(vars[i].name);
}

var1 和 var2 作为脚本变量,初始化为 nullptr 并使用"scriptvarwrapper"添加到编译时数组"vars"中。在"init_vars"中,脚本变量被初始化,然后可以通过访问"var1"和"var2"来使用(甚至不知道用于初始化它们的编译时数组)

非编译时但易于使用的解决方案:

class ScriptVar
{
public:
ScriptVar(const char *name_)
: name(name_)
{
vars.insert(this);
}
scriptvar* operator->()
{
return handle;
}
static void initVars()
{
for (auto var : vars)
var->handle = get_var(var->name);
}
private:
static std::set<ScriptVar*> vars;
const char *name;
scriptvar *handle;
};
const ScriptVar var1("namespace::var1");
const ScriptVar var2("namespace::var2");

每个定义的 ScriptVar 都在 ScriptVar::vars 中注册,调用 ScriptVar::initVars() 后,可以使用 -> 运算符访问所有定义的 ScriptVar。