天天看点

Apache Arrow源码分析(二)——类型的封装引言DataType类图结构和详细分析参考资料

@原创文章,转载请注明: 转载自 镜中影的技术博客

本文链接地址: 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基类。详细类图描述如下:

Apache Arrow源码分析(二)——类型的封装引言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

继续阅读