【问题标题】:Filling Struct From External file with Loop使用循环从外部文件填充结构
【发布时间】:2019-08-07 06:05:30
【问题描述】:

我有一个包含此表的 lua 脚本:

test = {}
test[1] = "Naya"
test[2] = 1000
test[3] = 1
test[4] = 20

我的 C++ 程序中有一个结构。打算填写该表中包含的信息:

//Enemy Struct
struct Enemy
{
    string name;
    int health;
    int family;
    int level;
} enemy;

为已知大小和已知键名的表填充结构很容易。

当有以下情况时,它变得更具挑战性:

  • 一个未知大小的表格(很容易找到表格长度)
  • 数字键名。 (只需使用 i 即可)
  • 填充结构(我遇到问题的部分)

我想要的是使用一个简单的 for 循环来填充这个 Struct,如下所示:

for (int i = 1; i < length; i++)
        {
            lua_pushnumber(L, i);
            lua_gettable(L, -2);
            if (lua_isnumber(L, -1))
                enemy.at(i)? = lua_tonumber(L, -1);
            else
                enemy.at(i) ? = lua_tostring(L, -1);

            lua_pop(L, 1);
        }

结构没有.at(x) 函数。因此我不能使用这个简单的循环来遍历结构来填充它。

我考虑过使用带有向量/结构的二维数组。但我不确定这是否有效。

我希望 Struct 使用循环填充来自 lua 脚本的数据。

【问题讨论】:

  • "Struct 没有 ".at(x)" 函数。" - 不。C++ 没有 reflection - 这似乎是您正在寻找的功能。

标签: c++ loops struct lua


【解决方案1】:

您需要某种反射才能动态获取索引 i 处的成员。
不幸的是,标准 c++ 中没有任何东西可以为您做到这一点。

有几种方法可以让它发挥作用:

  • 最简单的解决方案是只读取值并将它们直接分配给敌人对象,例如:
Enemy e;
lua_pushnumber(L, 1);
lua_gettable(L, -2);
e.name = lua_tostring(L, -1);
// ...

但是,如果您有很多成员,这将很快变得非常混乱。

  • 假设您可以对所有成员(例如所有字符串)使用相同的类型,您可以使用向量而不是 Enemy 结构,例如:
std::vector<std::string> enemy;
for(int i = 0; i < length; i++) {
  lua_pushnumber(L, i);
  lua_gettable(L, -2);
  enemy.push_back(lua_tostring(state, -1));
  lua_pop(L, 1);
}

此解决方案的明显缺点是您需要在向量中使用索引,而不是像 healthname 等花哨的名称。而且您仅限于单一类型。

#include <boost/fusion/adapted/struct/adapt_struct.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/algorithm/iteration.hpp>
#include <lua/lua.hpp>

struct Enemy
{
    std::string name;
    int health;
    int family;
    int level;
} enemy;

BOOST_FUSION_ADAPT_STRUCT(
    Enemy,
    (std::string, name)
    (int, health)
    (int, family)
    (int, level)
)

struct LuaVisitor {
    LuaVisitor(lua_State* state) : idx(1), state(state) {}

    void operator()(std::string& strVal) const {
        next();
        // TODO: check if top of stack is actually a string (might be nil if key doesn't exist in table)
        strVal = lua_tostring(state, -1);
        lua_pop(state, 1);
    }

    void operator()(int& intVal) const {
        next();
        // TODO: check if top of stack is actually a number (might be nil if key doesn't exist in table)
        intVal = lua_tonumber(state, -1);
        lua_pop(state, 1);
    }

    void next() const {
        lua_pushnumber(state, idx++);
        lua_gettable(state, -2);
    }

    mutable int idx;
    lua_State* state;
};


int main(int argc, char* argv[]) {
    // create a sample table
    lua_State* state = luaL_newstate();
    luaL_dostring(state, "return {\"Hello World\", 1337, 12, 123}");

    // read the enemy from the table
    Enemy e;
    boost::fusion::for_each(e, LuaVisitor(state));

    std::cout << "[Enemy] Name: " << e.name << ", health: " << e.health << ", family: " << e.family << ", level: " << e.level << std::endl;

    lua_close(state);
    return 0;
}

这将使您能够动态设置Enemy 结构的成员,但是boost 非常重,您需要使BOOST_FUSION_ADAPT_STRUCT 宏与实际结构保持同步。

  • 您还可以使用 lua 绑定库,例如luaaa 将您的 Enemy 对象直接映射到 lua(而不是传递一个表格):
#include <lua/luaaa.h>

struct Enemy
{
    Enemy() {}
    virtual ~Enemy() {}

    void init(std::string _name, int _health, int _family, int _level) {
        name = _name;
        health = _health;
        family = _family;
        level = _level;
    }

    std::string name;
    int health;
    int family;
    int level;
};

void myFunction(Enemy* e) {
    std::cout << "[Enemy] Name: " << e->name << ", health: " << e->health << ", family: " << e->family << ", level: " << e->level << std::endl;
}

int main(int argc, char* argv[]) {
    // init lua & bindings
    lua_State* state = luaL_newstate();
    luaaa::LuaClass<Enemy> luaEnemy(state, "Enemy");
    luaEnemy
        .ctor()
        .fun("init", &Enemy::init);
    luaaa::LuaModule mod(state, "sampleModule");
    mod.fun("myFunction", myFunction);

    luaL_dostring(state, R"(
      enemy = Enemy.new()
      enemy:init("My Enemy", 1, 2, 3)
      sampleModule.myFunction(enemy)
    )");

    lua_close(state);
    return 0;
}

那么就可以直接使用lua中的敌人对象了:

enemy = Enemy.new()
enemy:init("My Enemy", 1, 2, 3)
sampleModule.myFunction(enemy)

希望这些选项之一涵盖您的用例;)

【讨论】:

    【解决方案2】:

    这是否必须处理各种大小的信息?否则,您始终可以通过分配如下值来填充它:Enemy.name = ...;。或者可以选择将数据写入 .csv 文件(以逗号分隔的值,以防万一),然后将其读入结构。
    如果它是一次性的,你也可以像 Enemy enemy = Enemy{name, ...}; 这样初始化结构 希望这会有所帮助:)

    【讨论】:

    • 表在 lua 脚本中可以是任意大小。目的是避免为每个新键名手动编写相同且重复的代码。
    • 好的,那么我建议不要使用结构,而是使用向量之类的东西。对于向量,您可以通过 for(int i=0;i&lt;x;i++)vector.push_back(lua_pushback); 或类似的方式对其进行迭代。或者,使用 HashMap,这样您就可以通过键值访问它,而不必记住存储它们的顺序。
    【解决方案3】:

    我认为有三种方法可以做到这一点:

    1)最简单,创建一个vector,逐行读取文件,解析...=?后面的内容,然后放入vector中,可能全部转成字符串。因此,您将使用 std::vector&lt;string&gt; 来存储所有内容。

    2) 好的难度,您可以使用class 代替,并在其中动态填充std::vector&lt;Object*&gt;。同上,但现在可以另存为intstring等。

    3) 最难,你需要实现一种反射形式。这是我能找到的:How can I add reflection to a C++ application?

    无论如何,这是一本好书。

    【讨论】:

    • 你可能对字符串向量是正确的,我想我可以在之后立即将数组转换为正确的类型。
    猜你喜欢
    • 2012-01-22
    • 2019-08-13
    • 1970-01-01
    • 1970-01-01
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多