【问题标题】:Incrementally build vector of variable pointers at compile time在编译时增量构建变量指针向量
【发布时间】:2018-04-18 14:03:35
【问题描述】:

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

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 类或使用其他一些方法来允许我在任何时候声明这些全局句柄变量,而且还可以在编译时为这些变量构建一个节省空间的向量,那就太好了我可以稍后访问:

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 函数,它以某种方式将所有这些变量链接到一个列表中,或者最好只产生一个指向内存中所有 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 系统上工作一样——它们通过指针收集到自己的部分,所有这些部分都是在链接时连接,最后带有一个特殊的对象文件,用于传递哨兵结束值。

【问题讨论】:

  • 能否在初始化脚本语言后立即使用 get_var 缓存变量?
  • 也许std::map 可以解决问题
  • @AndrewKashpur 如果您的意思是 get_var 返回的句柄是否无限期保持有效,则此示例的答案是肯定的。问题是在程序初始化时不能调用get_var,否则构造函数可以简单地调用它。
  • @user2176127 std::map 不能在编译时使用,可以吗?
  • 您的问题可以通过“反射”解决,但愚蠢的 C++ 目前不提供此功能。

标签: c++ c++14


【解决方案1】:

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

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

scriptvarwrapper 也可以这样做:

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 作为 scriptvars,初始化为 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 都可以使用 -> 操作符访问。

【讨论】:

  • 这些都是不错的选择,但它们都没有在编译时计算列表——前几个需要我手动完成,最后一个是在运行时。我会尝试改进我的问题,希望它变得更清晰。
  • 我认为你真正需要的是某种提供反射功能的预处理器。恐怕至少在 C++20 之前,标准 c++ 都无法做到这一点(参见meetingcpp.com/blog/items/…
猜你喜欢
  • 2015-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-04
  • 2018-08-28
  • 2010-11-11
  • 2021-10-15
相关资源
最近更新 更多