今天来看下 folly 库中的 dynamic 工具类。这个类的目的是想实现 c++ 的动态类型,类似 python、lua 那样。之前也大致看了 lua 弱类型的实现,这次可以来比较下。
# 1. 使用方法
下面是个官方的例子。
当你使用 using folly::dynamic;
后,你可以像下面这样使用 dynamic:
dynamic twelve = 12; // creates a dynamic that holds an integer | |
dynamic str = "string"; // yep, this one is an fbstring | |
// A few other types. | |
dynamic nul = nullptr; | |
dynamic boolean = false; | |
// Arrays can be initialized with dynamic::array. | |
dynamic array = dynamic::array("array ", "of ", 4, " elements"); | |
assert(array.size() == 4); | |
dynamic emptyArray = dynamic::array; | |
assert(emptyArray.empty()); | |
// Maps from dynamics to dynamics are called objects. The | |
// dynamic::object constant is how you make an empty map from dynamics | |
// to dynamics. | |
dynamic map = dynamic::object; | |
map["something"] = 12; | |
map["another_something"] = map["something"] * 2; | |
// Dynamic objects may be initialized this way | |
dynamic map2 = dynamic::object("something", 12)("another_something", 24); |
可以看到基本常用的类型都有支持到,能满足大多数场景的使用。
# 2. 实现方式
# i. 初始化
每次赋值其实是隐式地调用了 dynamic 对应的构造函数:
# string
/* | |
* String compatibility constructors. | |
*/ | |
/* implicit */ dynamic(std::nullptr_t); | |
/* implicit */ dynamic(char const* val); | |
/* implicit */ dynamic(std::string val); | |
template < | |
typename Stringish, | |
typename = std::enable_if_t< | |
is_detected_v<dynamic_detail::detect_construct_string, Stringish>>> | |
/* implicit */ dynamic(Stringish&& s); |
以上是针对 string 类型的数据的构造函数,比较特别的是最后声明了一个模板构造函数,传入一个 Stringish 对象(可以字符串化的对象),需要有 data 和 size 对象,用于构造 string。
template <typename Stringish, typename> | |
inline dynamic::dynamic(Stringish&& s) : type_(STRING) { | |
new (&u_.string) std::string(s.data(), s.size()); | |
} |
# Object
/* | |
* This is part of the plumbing for array() and object(), above. | |
* Used to create a new array or object dynamic. | |
*/ | |
/* implicit */ dynamic(void (*)(EmptyArrayTag)); | |
/* implicit */ dynamic(ObjectMaker (*)()); | |
/* implicit */ dynamic(ObjectMaker const&) = delete; | |
/* implicit */ dynamic(ObjectMaker&&); |
以上这些构造函数是为了适配数组、map 和一般对象,通过 ObjectMaker 来方便对应对象的构造。
# Num
/* | |
* Constructors for integral and float types. | |
* Other types are SFINAEd out with NumericTypeHelper. | |
*/ | |
template <class T, class NumericType = typename NumericTypeHelper<T>::type> | |
/* implicit */ dynamic(T t); |
针对整型和浮点数,使用了一个模板函数和 NumericTypeHelper 来帮助构造和区分 Num 的数据。
# Iterator
/* | |
* If v is vector<bool>, v[idx] is a proxy object implicitly convertible to | |
* bool. Calling a function f(dynamic) with f(v[idx]) would require a double | |
* implicit conversion (reference -> bool -> dynamic) which is not allowed, | |
* hence we explicitly accept the reference proxy. | |
*/ | |
/* implicit */ dynamic(std::vector<bool>::reference val); | |
/* implicit */ dynamic(VectorBoolConstRefCtorType val); | |
/* | |
* Create a dynamic that is an array of the values from the supplied | |
* iterator range. | |
*/ | |
template <class Iterator> | |
explicit dynamic(Iterator first, Iterator last); |
以上是迭代器的构造函数。
# ii. 类型维护
在创建一个 dynamic 对象后会使用一个成员 type_
来记录类型,在对应构造函数中会初始化该成员:
inline dynamic::dynamic() : dynamic(nullptr) {} | |
inline dynamic::dynamic(std::nullptr_t) : type_(NULLT) {} | |
inline dynamic::dynamic(void (*)(EmptyArrayTag)) : type_(ARRAY) { | |
new (&u_.array) Array(); | |
} | |
inline dynamic::dynamic(ObjectMaker (*)()) : type_(OBJECT) { | |
new (getAddress<ObjectImpl>()) ObjectImpl(); | |
} | |
inline dynamic::dynamic(char const* s) : type_(STRING) { | |
new (&u_.string) std::string(s); | |
} | |
... |
有了 type_
存储类型就可以用更方便地实现方法,同时也因此可以提供类型的接口,方便调用者使用:
/* | |
* Returns true if this dynamic is of the specified type. | |
*/ | |
bool isString() const; | |
bool isObject() const; | |
bool isBool() const; | |
bool isNull() const; | |
bool isArray() const; | |
bool isDouble() const; | |
bool isInt() const; | |
/* | |
* Returns: isInt() || isDouble(). | |
*/ | |
bool isNumber() const; | |
/* | |
* Returns the type of this dynamic. | |
*/ | |
Type type() const; |
# iii.ObjectMaker
ObjectMaker 是为了方便构造对象,大致代码如下:
// Helper object for creating objects conveniently. See object and | |
// the dynamic::dynamic(ObjectMaker&&) ctor. | |
struct dynamic::ObjectMaker { | |
friend struct dynamic; | |
explicit ObjectMaker() : val_(dynamic::object) {} | |
explicit ObjectMaker(dynamic key, dynamic val) : val_(dynamic::object) { | |
val_.insert(std::move(key), std::move(val)); | |
} | |
// Make sure no one tries to save one of these into an lvalue with | |
// auto or anything like that. | |
ObjectMaker(ObjectMaker&&) = default; | |
ObjectMaker(ObjectMaker const&) = delete; | |
ObjectMaker& operator=(ObjectMaker const&) = delete; | |
ObjectMaker& operator=(ObjectMaker&&) = delete; | |
// This returns an rvalue-reference instead of an lvalue-reference | |
// to allow constructs like this to moved instead of copied: | |
// dynamic a = dynamic::object("a", "b")("c", "d") | |
ObjectMaker&& operator()(dynamic key, dynamic val) { | |
val_.insert(std::move(key), std::move(val)); | |
return std::move(*this); | |
} | |
private: | |
dynamic val_; | |
}; |
比较特别的是其中的构造函数仅提供了 const 引用和右值的形式。使用 const 引用可以最小化权限,避免一些不必要的修改引起的错误。使用右值形式可以避免拷贝,提高性能。
# iv.NumericTypeHelper
NumericTypeHelper 这个类是 folly 为了方便处理整型和浮点数这两类 Num 类型的数据。
dynamic 中将整型统一使用 int64 类型,浮点型统一使用 double 类型,会通过以下的机制去区分:
template < | |
class T, | |
class NumericType /* = typename NumericTypeHelper<T>::type */> | |
dynamic::dynamic(T t) { | |
type_ = TypeInfo<NumericType>::type; | |
new (getAddress<NumericType>()) NumericType(NumericType(t)); | |
} |
可以看到构造时会使用 TypeInfo<NumericType>::type
作为 type,默认情况下则是使用 typename NumericTypeHelper<T>
中生成的 type。NumericTypeHelper 针对整型、bool、float、double,会生成不同的 type:
template <class T> | |
struct dynamic::NumericTypeHelper< | |
T, | |
typename std::enable_if<std::is_integral<T>::value>::type> { | |
static_assert( | |
!kIsObjC || sizeof(T) > sizeof(char), | |
"char-sized types are ambiguous in objc; cast to bool or wider type"); | |
using type = int64_t; | |
}; | |
template <> | |
struct dynamic::NumericTypeHelper<bool> { | |
using type = bool; | |
}; | |
template <> | |
struct dynamic::NumericTypeHelper<float> { | |
using type = double; | |
}; | |
template <> | |
struct dynamic::NumericTypeHelper<double> { | |
using type = double; | |
}; |
生成对应 NumericTypeHelper 后就在构造函数中使用 TypeInfo 去取到类型,TypeInfo 也是上面定义的一个类似的 struct,可以根据数据的 type 获取对应的 dynamic 中的 type:
#define FOLLY_DYNAMIC_DEC_TYPEINFO(T, val) \ | |
template <> \ | |
struct dynamic::TypeInfo<T> { \ | |
static const char* const name; \ | |
static constexpr dynamic::Type type = val; \ | |
}; \ | |
// | |
FOLLY_DYNAMIC_DEC_TYPEINFO(std::nullptr_t, dynamic::NULLT) | |
FOLLY_DYNAMIC_DEC_TYPEINFO(bool, dynamic::BOOL) | |
FOLLY_DYNAMIC_DEC_TYPEINFO(std::string, dynamic::STRING) | |
FOLLY_DYNAMIC_DEC_TYPEINFO(dynamic::Array, dynamic::ARRAY) | |
FOLLY_DYNAMIC_DEC_TYPEINFO(double, dynamic::DOUBLE) | |
FOLLY_DYNAMIC_DEC_TYPEINFO(int64_t, dynamic::INT64) | |
FOLLY_DYNAMIC_DEC_TYPEINFO(dynamic::ObjectImpl, dynamic::OBJECT) |
# 3. 总结
folly 的 dynamic 实现还是相当复杂的,代码中有非常多对模板和 SFINAE 的使用,看起来还是蛮吃力的,不过通过这次也学到了不少,下次写代码的时候尝试着多去用用看。总的来说,folly 的 dynamic 的实现和 Lua 的动态类型很像,在初始化时赋予类型,这样使用时会比 boost::any 和或 std::any 这种通过类型擦除实现的方便很多。
# 参考资料
- facebook/folly