windoze 发表于 2015-10-25 14:04

前两天有人提到C++没反射,写了个实验版

本帖最后由 windoze 于 2015-10-26 00:34 编辑

C++的确没反射,我一开始搞了一个code generator,后来发现这么搞对于build system实在影响太大,所以后来我干脆转去用宏了。
代码在 https://github.com/windoze/cxl
这是一个header only库,大概这么用:
如果你有一个struct SC
struct SC {
    int m1;
    const double m2;

    SC(int mm1, double mm2, int mm3) : m1(mm1), m2(mm2), m3(mm3) {}
    int get_m3() const { return m3; }
    void set_m3(int x) { m3 = x; }

    int get_m4() const { return 100; }
private:
    int m3;
};
在外面定义几个宏:
CXL_EXT_BEGIN_REFLECTED(SC, 4)
      CXL_REFLECTED_MEMBER(0, m1)
      CXL_REFLECTED_MEMBER(1, m2, CXL_SQL_FIELD("field2"))
      CXL_REFLECTED_ATTRIBUTE(2, int, m3, CXL_MEM_GETTER(get_m3), CXL_MEM_SETTER(set_m3))
      CXL_REFLECTED_RO_ATTRIBUTE(3, int, m4, CXL_MEM_GETTER(get_m4))
CXL_EXT_END_REFLECTED()
然后这个struct SC就可以支持反射了,比如你可以动态读写field:
SC sc(10, 5.5, 84);
cxl::set(0, sc, 42);
assert(sc.m1==42);
assert(cxl::get<int>(0, sc)==42);
如果,field不能写或不能用你提供的类型读写,则会在运行时抛出std::bad_cast,而不是编译错误,比如:
try {
      cxl::set(1, sc, 7.5);
      cxl::get<std::string>(0, sc);
      assert(false);
} catch(std::bad_cast&) {
}
你还可以访问每个字段的一些metadata,目前预定义的有name, key, sql_table, sql_field, json_key, xml_node, xml_namespace, csv_field, 如要扩展可以在cxl/reflection/reflection_impl.hpp里加,三句话就行。
这些metadata可以用来干一些有用的事,比如:
    // 玩具SQL生成器
    std::string keys;
    for(size_t i=0; i<cxl::tuple_size<SC>::value; i++) {
      if(!keys.empty()) keys+=", ";
      keys+=get_element_sql_field<SC>(i);
    }
    keys = "SELECT " + keys + " FROM " + cxl::get_sql_table<SC>() + " WHERE "
         + cxl::get_element_sql_field<SC>(0) + "=?";
    assert(keys == "SELECT m1, field2, m3, m4 FROM SC WHERE m1=?");

fender0107401 发表于 2015-10-25 16:15

哈哈,阿猫猛啊。

windoze 发表于 2015-10-25 16:28

回复 2# fender0107401

之前就是你发帖子说这事的吧,我写这个东西就是验证一下思路,,因为我倾向于把尽量多的事放在编译期做完,所以选择了一个把任意结构体往std::tuple上靠拢的方案,运行时的动态性用variant。
你试试看这个方向可行不可行
@yulihua49

fender0107401 发表于 2015-10-25 17:25

回复 3# windoze

是啊,之前就是我希望C++里面能有反射。那个时候想用C++来代替PHP来做网站的后端。

   

cokeboL 发表于 2015-10-25 20:50

楼主雄壮V5,87侧漏!

truekbcl 发表于 2015-10-26 13:14

sf里面有几个cpp的反射库。楼主的宏里面加如数字,以及索引,对于维护比较麻烦,可以不用。对于自定义的成员,写traits解决。我个人看法,函数反射基本没有什么用处。要处理成员,外部提供算法比较好,而且好扩展,好维护。

yulihua49 发表于 2015-10-26 14:34

本帖最后由 yulihua49 于 2015-10-26 15:01 编辑

windoze 发表于 2015-10-25 16:28 static/image/common/back.gif
回复 2# fender0107401

之前就是你发帖子说这事的吧,我写这个东西就是验证一下思路,,因为我倾向于把 ...
我在做数据库时,写几个通用函数:
select,fetch,insert,update,delete,execute。。。。。。
这些函数在编译时并不知道具体的数据结构。
他们必须解决自动绑定的问题。否则,动辄上百的列都让用户去做,包装的意义少了一大半。

一个例子,如果写一个类似ORACLE的sqlldr的东西,能够加载任意表进数据库,或者反之。
用你这方法,我不知道怎么弄。运行时人家才告诉我表名和对应的数据(编译期解决不了这些问题)。
退一步讲,有时应用程序是知道数据结构的,他把数据提交给上述函数,那些函数是不知道这些数据的结构的。
真反射可以解决上述问题,但是:
一个对象里的内容,有些是要对应到数据库,有些不是,有些临时不是,怎么表示?
一个对象里的内容,内存的类型和格式与数据库的不符,怎么办?
一个对象里的内容,有些是要序列化到JSON或XML,有些不是,有些临时不是,怎么表示?
需要类似JAVAbean的对象,还要加上一些属性来表示。


yulihua49 发表于 2015-10-26 15:04

本帖最后由 yulihua49 于 2015-10-26 15:06 编辑

truekbcl 发表于 2015-10-26 13:14 static/image/common/back.gif
sf里面有几个cpp的反射库。楼主的宏里面加如数字,以及索引,对于维护比较麻烦,可以不用。对于自定义的成员 ...

把Object序列化成JSON、XML,或反之。
各种,很多的Object,只想写两个函数:序列化和反序列化。处理各种对象。怎么办?
现在的状况,每种Object都要写两个,太不优雅了!

windoze 发表于 2015-10-26 17:08

回复 7# yulihua49

你的理解有误,尽管“类型”可以有任意多个,但这些“类型”并不是凭空生成的,每个类型都是基本类型的排列组合,这一点就算对于数据库也一样。
反射的意义不在于处理任意类型,事实上你也没办法写出直接处理任意类型的程序,但反射能告诉你一个特定的类型是由哪些基本类型排列组合而成的,这样你就可以用一组处理基本类型的代码处理任意类型。
以std::tuple为例,它本身是一个template,可以包含任意多个任意类型的成员,如果我要写一个to_json以支持任意一个tuple,我可以这么做:
std::string to_json(int v) {...}
std::string to_json(double v) {...}
std::string to_json(const std::string v) { return std::string("\"") + escape(v) + "\""; }   // escape生成转义后的字符串
...
// 以上为处理基本类型的代码


template<T> std::string to_json(const std::vector<T> &v) {
    std::string s("[");
    bool first=true;
    for(auto &&e: v) {
      if(first) first=false;
      else s+=", ";
      s+=to_json(e);
    }
    s+= "]";
    return s;
}

template<K, V> std::string to_json(const std::map<K, V> &v) {
    std::string s("{");
    bool first=true;
    for(auto &&e: v) {
      if(first) first=false;
      else s+=", ";
      s+= to_json(e.first) + ":" + to_json(e.second);
    }
    s+= "}";
    return s;
}
//以上为处理STL容器的代码


// 遍历tuple
template<size_t I, size_t N> struct tuple_to_json_impl {
    template<typename T> std::string operator()(const T& t) const {
      std::string s(to_json(std::get<I>(t)));
      // Not the last element, add a separator
      if (I<N-1) s+=", ";
      s+=tuple_to_json_impl<I+1, N>()(t);
      return s;
    }
};
// 遍历tuple结束
template<size_t N> struct tuple_to_json_impl<N, N> {
    template<typename T> std::string operator()(const T& t) const {}
};
typename<typename ...T> std::string to_json(const std::tuple<T...> &t) {
    std::string s("[");
    s+= tuple_to_json_impl<std::tuple<T...>>()(t) + "]";
    return s;
}
有了上面这段代码,你就可以用一个叫做"to_json"的函数把基本类型和任意一个tuple类型都变成JSON string,不需要针对每一个不同的tuple都做一遍。
当然,上面这段代码没有运行时的动态性,尽管从代码角度来说它可以处理任意一个tuple,但它所处理的每一个tuple其实都是在编译期确定下来的。

想要在运行时的动态性,可以用boost::variant/cxl::variant(这两个东西功能类似,API也基本一样,我的cxl::variant差不多是一个C++11版的boost::variant,加了些小功能)
现在加入一个variant版的to_json:
struct to_json_visitor {
    template<typename T> std::string operator(const T &t) const {
      return to_json(t);
    }
};
template<typename ...T> to_json(const cxl::variant<T...> &t) {
    return t.apply_visitor(to_json_visitor());
}
好了,有了以上的东西,我们可以定义一个JSON数据结构:
struct JSON_object;
typedef cxl::variant<std::nullptr_t, int, double, std::string, std::vector<cxl::recursive_wrapper<JSON_object>>, std::map<cxl::recursive_wrapper<JSON_object>>> JSON_value;
struct JSON_object {
    template<typename T>
    JSON_object(const T&t) : value(t) {}
    JSON_value value;
};
//再加两个用于支持null和任意JSON_object的函数
std::string to_json(std::nullptr_t) { return "null"; }
std::string to_json(const JSON_object &obj) { return to_json(obj.value); }
有了上面这些代码,我们可以用JSON_object存放任意结构的JSON对象,比如:
JSON_object obj1=10;
JSON_object obj2=5.6;
JSON_object obj3=std::vector<JSON_object>{1, "this is a string", std::map<JSON_object, JSON_object>{{"key1", "value1"}, {"key2", 200}}};
而且还可以用to_json把它们变成JSON string。

yulihua49 发表于 2015-10-27 14:43

本帖最后由 yulihua49 于 2015-10-27 15:34 编辑

windoze 发表于 2015-10-26 17:08 static/image/common/back.gif
回复 7# yulihua49

你的理解有误,尽管“类型”可以有任意多个,但这些“类型”并不是凭空生成的,每个 ...

怎样知道我的g++是否支持C++11呢?
看的晕晕,好像tuple可以用于存储模板。
那个get<1>,get<2>也晕,不是<type>吗?怎么玩起下标来了?
页: [1] 2 3
查看完整版本: 前两天有人提到C++没反射,写了个实验版