【问题标题】:C++ Automatically Exposing Class Members to Multiple APIsC++ 自动向多个 API 公开类成员
【发布时间】:2019-11-13 07:29:03
【问题描述】:

短: 有许多聪明的库可以让您为各种目的公开一个类的成员,例如序列化、暴露于 lua 环境等。问题是对于每个聪明的解决方案,您必须为每个解决方案注册一个类的东西api,这会导致大量的工作重复和样板。

我可以通过什么方式注册一个类及其成员和方法ONCE 并附加某种标志并获得各种自动生成的方法

长: 假设我有一堂课:

class C {
public:
    int i;  // Some int
    vec3 v; // Some non-POD value
    int function(int foo, char bar) // Some function
    int hidden; // Some internal stuff I don't want exposed
        }

我想做的是能够在声明时标记各种成员和方法,以便将它们自动放入各种样板中:

所以声明可能看起来像:

class C {
public:
    LUAREG JSON BINSER int i; // Marks member "i" to be exposed to the lua enviroment,
                              // JSON serialisable, binary serialisable
    JSON vec3 v; // Only exposed to JSON function

    LUAREG void someFunction() {} // Marks this method to be exposed to the lua enviroment
    }

或者写一个注册函数来完成所有的声明:

void C::register_class() {
    registrator::register_class<C>("C")
        .addData("i", &i, FLAG_LUA | FLAG_JSON | FLAG_BINARY_SERIALISABLE)
        .addFunction("someFunction", &someFunction, FLAG_LUA)
    .end_class()
    }

(我曾多次看到这种模式——它有名字吗?) 我希望能够为所述类自动生成各种样板函数。例如:

/// Adds all fields with FLAG_JSON to a "json" object to be later serialised
void toJson(const C & c, json & j) {
    toJson(c.i, "i", j);
    toJson(c.v, "v", j);
    }

/// Binary seralises all members with flag FLAG_BINARY_SERIALISABLE and stores the result in s
void serialise(const C & c, string & s) {
    serialise(c.i, s);
    serialise(c.v, s);
    }

/// Registers all stuff with flag FLAG_LUA to some lua environment
void register_lua(lua_State * const L) {
        luaReg_FetchContext(L)::addClass("C")
            .addData("x", &C::x).comment("Some int") // generates documentation of "int x : Some int"
            .addData("v", &C::v).comment("Some vector")
            .addFunction("function", &C::function).comment("some function") // generates: "int function(int, char) : some function"
        .endClass()
        }

/// Register to some *other* lua environment (such as a read only context for rendering)
void register_lua_readonly(lua_State * const L2) {
        luaReg_FetchContext(L)::addClass("C")
            .addData("x", &C::x, false).comment("Some int")
        .endClass()
        }

/// Generates some user readable breakdown of all members
string dump() {
    ...
    }

现在你可以看到很多类,手动操作会变得非常乏味、臃肿且容易出错

解决此问题的最佳方法是什么?我很想使用宏来吐出函数,但不知道如何进入注册阶段。干杯!

【问题讨论】:

  • 回答您的小问题:使用方法零散构建的类的概念称为builder pattern。将参数链接为方法的 API 样式称为 fluent api
  • 集思广益如何完成第一个样式,您可以尝试使用模板来构建特征:lua_reg&lt;json&lt;binser&lt;T&gt;&gt;&gt;,然后像 Boost PFR 这样的东西来聚合特征以进行注册、序列化等。
  • @parktomatomi 一方面,它不会提供变量的名称
  • @darune 好点,我猜宏是唯一的选择。

标签: c++ boilerplate


【解决方案1】:

我有一个解决方案 - 我推出了自己的反射库 - https://github.com/SnapperTT/STTR

class C {
public:
    int i;
    double d;
    int func(int z) const {
        return z;
        }

    static int static_var;
    static int static_func() { return 5; }
    };
int C::static_var = 7;

struct D : public C {
    int di;
    };

int main(int argc, char ** argv) {
    // Registration
    sttr::RegNamespace mNamespace("testns");
    mNamespace.beginClass<C>("C")
        .regField(&C::i, "i")
        .regField(&C::d, "d")
            .setUserFlags(0x00F00BAA) // Sets the user flags for the last added item
            .setUserString("You can use this for whatever")
            .setUserData((void *) &someStruct)
        .regField(&C::func, "func")

        .regField(&C::static_var, "static_var")
        .regField(&C::static_func, "static_func")
    .endClass()
    .findClass("C").beginClass<D>("D") // Class inherritance. Also you can just beginClass<D>("D") inside C
        .regField(&D::di, "di")
    .endClass();
}

此解决方案的关键是我可以将标志附加到注册成员(因此 LUA 可以是 1 &lt;&lt; 0,JSON 可以是 1 &lt;&lt; 1,等等。

自动 json 序列化和 lua 注册是通过访问者完成的。访问者检查成员是否具有相关标志,然后执行其操作或跳到下一个成员。

我也尝试过使用 RTTR:https://github.com/rttrorg/rttr - 这是一个不错的库,尽管它很庞大。我推出了自己的库,因为我不喜欢访问者界面和大量的模板错误消息。

最后,自动文档是通过运行一个脚本来完成的,该脚本执行一堆正则表达式查找和替换来挑选typename Foo; /// Some documentation 的情况并创建一个static const char * const docv_Foo = "Some documentation"; 并注册它。这确实为编译增加了一个中间步骤,但一点也不坏。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-06
    • 2010-09-21
    • 2011-02-05
    • 2013-01-21
    • 1970-01-01
    • 2012-01-03
    • 2016-11-19
    • 2013-03-25
    相关资源
    最近更新 更多