@原创文章,转载请注明: 转载自 镜中影的技术博客
本文链接地址: Apache Arrow源码分析(二)——类型的封装
URL:http://blog.csdn.net/linkpark1904/article/details/51000719
引言
对于向量容器而言,需要适配底层多种数据类型。C++在语言层面提供了诸如int,double,float,char等基础类型,但是对于程序库而言,需要自己定义多种类型的数据结构,当然是构建在C++语言层面提供的基础数据类型之上,就需要对数据类型进行封装。Arrow项目中亦有涉及对数据类型(DataType)的封装,采用C++11实现,很多新特性值得我们学习和借鉴,这里单独拿出来分析。
DataType类图结构和详细分析
DataType的定义从struct type开始,struct type中包含enum 变量type用来描述arrow支持的逻辑类型,抽象出类DataType来表示类型基类,所有自定义类型都继承自DataType基类。详细类图描述如下:

具体Type类的代码如下所示(Type.h):
struct Type {
enum type {
// A degenerate NULL type represented as 0 bytes/bits
NA = 0,
// A boolean value represented as 1 bit
BOOL = 1,
// Little-endian integer types
UINT8 = 2,
INT8 = 3,
UINT16 = 4,
INT16 = 5,
UINT32 = 6,
INT32 = 7,
UINT64 = 8,
INT64 = 9,
....
// Exact timestamp encoded with int64 since UNIX epoch
// Default unit millisecond
TIMESTAMP = 17,
// Timestamp as double seconds since the UNIX epoch
TIMESTAMP_DOUBLE = 18,
// Exact time encoded with int64, default unit millisecond
TIME = 19,
// Precision- and scale-based decimal type. Storage type depends on the
// parameters.
DECIMAL = 20,
// Decimal value encoded as a text string
DECIMAL_TEXT = 21,
...
}
};
其中只包含了一组枚举类型,对于DataType类的实现代码如下所示:
struct DataType {
Type::type type;
std::vector<std::shared_ptr<Field>> children_;
explicit DataType(Type::type type) :
type(type) {}
virtual ~DataType();
bool Equals(const DataType* other) {
// Call with a pointer so more friendly to subclasses
return this == other || (this->type == other->type);
}
bool Equals(const std::shared_ptr<DataType>& other) {
return Equals(other.get());
}
const std::shared_ptr<Field>& child(int i) const {
return children_[i];
}
int num_children() const {
return children_.size();
}
virtual int value_size() const {
return -;
}
virtual std::string ToString() const = ;
};
typedef std::shared_ptr<DataType> TypePtr;
基类中定义了一系列接口和方法,包括类型的判等,类型长度等相关方法,其中关于成员变量
std::vector<std::shared_ptr<Field>> children_
代码注释里给出的解释是
A field is a piece of metadata that includes (for now) a name and a data type
。 另外,关于DataType类的子类,有四个PrimitiveType,StringType,ListType,StructType,这里PrimitiveType抽象为系统本身提供的变量类型例如int32_t,double,float等,PrimitiveType具体实现代码如下:
template <typename Derived>
struct PrimitiveType : public DataType {
PrimitiveType() : DataType(Derived::type_enum) {}
std::string ToString() const override;
};
template <typename Derived>
inline std::string PrimitiveType<Derived>::ToString() const {
std::string result(static_cast<const Derived*>(this)->name());
return result;
}
这段代码中出现了一个C++11引入的关键字override,显示的告诉编译器,子类PrimitiveType需要重写基类DataType的ToString方法。对于PrimitiveType子类NullType,BooleanType等的实现,Arrow代码中的做法值得借鉴,观察可知,PrimitiveType子类很多功能代码都是重复的,为了避免重复,Arrow利用宏定义来定义公共部分代码,具体实现如下:
#define PRIMITIVE_DECL(TYPENAME, C_TYPE, ENUM, SIZE, NAME) \
typedef C_TYPE c_type; \
static constexpr Type::type type_enum = Type::ENUM; \
\
TYPENAME() \
: PrimitiveType<TYPENAME>() {} \
\
virtual int value_size() const { \
return SIZE; \
} \
\
static const char* name() { \
return NAME; \
}
struct NullType : public PrimitiveType<NullType> {
PRIMITIVE_DECL(NullType, void, NA, , "null");
};
struct BooleanType : public PrimitiveType<BooleanType> {
PRIMITIVE_DECL(BooleanType, uint8_t, BOOL, , "bool");
};
struct UInt8Type : public PrimitiveType<UInt8Type> {
PRIMITIVE_DECL(UInt8Type, uint8_t, UINT8, , "uint8");
};
具体说明,上述代码中BooleanType通过宏定义展开实际上是这样的:
struct BooleanType : public PrimitiveType<BooleanType> {
typedef uint8_t c_type;
static constexpr Type::type type_enum = Type::BOOL;
BooleanType()
: PrimitiveType<BooleanType>() {}
virtual int value_size() const {
return ;
}
static const char* name() {
return "bool";
}
};
这种写法,能够用三行代码来定义一个类,确实比较简洁。另外这里出现了C++11的又一个关键字,constexpr 是常量表达式关键字,其作用在于告诉编译器该类型变量是常量,于是在编译期就可以确定该变量的值。这里Type::type是枚举类型,所以在编译期就可以确定其值(表示一种具体的类型)。
对于非系统类型变量的封装,arrow抽出了两种类型,ListType和StringType
struct ListType : public DataType {
// List can contain any other logical value type
explicit ListType(const std::shared_ptr<DataType>& value_type)
: DataType(Type::LIST) {
children = {std::make_shared<Field>("item", value_type)};
}
explicit ListType(const std::shared_ptr<Field>& value_field)
: DataType(Type::LIST) {
children = {value_field};
}
const std::shared_ptr<Field>& value_field() const {
return children[];
}
const std::shared_ptr<DataType>& value_type() const {
return children[]->type;
}
static char const *name() {
return "list";
}
std::string ToString() const override;
};
这里,官方对ListType定义为
List can contain any other logical value type
于是children_成员变量就有了用武之地,用来表示链表中存储的元素(item)的类型,对于ListType的测试代码如下所示(List-test.cc):
TEST(TypesTest, TestListType) {
std::shared_ptr<DataType> vt = std::make_shared<UInt8Type>();
ListType list_type(vt);
ASSERT_EQ(list_type.type, Type::LIST);
ASSERT_EQ(list_type.name(), string("list"));
ASSERT_EQ(list_type.ToString(), string("list<item: uint8>"));
ASSERT_EQ(list_type.value_type()->type, vt->type);
ASSERT_EQ(list_type.value_type()->type, vt->type);
std::shared_ptr<DataType> st = std::make_shared<StringType>();
std::shared_ptr<DataType> lt = std::make_shared<ListType>(st);
ASSERT_EQ(lt->ToString(), string("list<item: string>"));
ListType lt2(lt);
ASSERT_EQ(lt2.ToString(), string("list<item: list<item: string>>"));
}
可以看出,该List的item类型为uint8。另一方面对于StringType和StructType的实现,Arrow只是单纯的继承DataType,没有做其他特殊的操作,就不再赘述了。
参考资料
深入理解C++11