[翻译]理解Qt容器:STL VS QTL(三)——类型系统 和其他处理

接上文翻译,说明在上文:[翻译]理解Qt容器:STL VS QTL(二)——迭代器

QTypeInfo

我们同样需要说下类型系统来去最优化使用Qt容器,除非你不想为你自己的类型去优化效率和内存占用:QTypeInfo.

QTypeInfo 是一个Traits Class ,为大多数Qt容器去开启已知的优化。

QTypeInfo 为你自己的类型指定的接口是:Q_DECLARE_TYPEINFO( Type, Flags ),为你自己的Type去指定一个属性。

两个因QTypeInfo去优化的是为POD类型(指定Flags = Q_PRIMITIVE_TYPE)和可以直接使用std::memcpy()去代替复制构造的类型(Flags = Q_MOVABLE_TYPE),我们可以看到这是后者真正的区别在Qt容器中,特别是在QList中。

但是,我们什么时候去为你的类型定义Q_PRIMITIVE_TYPE 或者Q_MOVABLE_TYPE呢?(第三个Flags 是Q_COMPLEX_TYPE,这个是默认类型,你不需要特意去指定,除非你不想别人去指定其他类型。)

  •  Q_PRIMITIVE_TYPE 对于所有POD(在 C++03检测) 类型。简化理解,POD也就是将作为C类型编译: 内置类型、 C数组和结构体。(Qt5帮助文档上说:没有构造/析构函数,每一个复制实例都需要使用 memcpy()去创建)
  • Q_MOVABLE_TYPE  这个适用于大多数类,可以移动的类型,可以再内存中重新定位,但是类的内容不会变。(这儿使用了移动,但是这和C++11的move语义是完全不同的),这对绝大多数类型是可以使用的。一个不能使用这个类型的例子是 :pimpl用法的类(Q-D指针)。如果公共类移动了位置,但是q-pointer指向的还是旧的位置。值得欣慰的是,这些类一般不直接作为容器的成员,只是他们指针作为成员(当然其指针类型是Q_PRIMITIVE_TYPE)。(Qt5帮助文档上说:类型具有构造/析构函数,但是可以用memcpy()进行移动。)

提示:为你的enums和QFlags声明Q_PRIMITIVE_TYPE属性,如果你可能在Qt容器中使用它。

提示:为你的值类型( value-types )声明为Q_MOVABLE_TYPE 属性。

提示:为你的值类型( value-types )声明为Q_MOVABLE_TYPE 属性。

在这一节我们将会看到各种容器依据这个属性进行的何种优化:

也许你想使用这些信息在你自己的容器类中,你可以使用QTypeInfo trait class。但是,不行的是,QTypeInfo的api没有与我们使用Q_DECLARE_TYPEINFO的属性相同的。所以,这儿有10,000ft 个观点:

  •  QTypeInfo<T>::isComplex 这是个编译期确定的,如果是你需要为T类型运行析构和构造函数则为true,否则不需要的则为false。当为Q_MOVABLE_TYPE 和 Q_COMPLEX_TYPE结构都为true。
  • QTypeInfo<T>::isStatic  这个也是编译期确定的,当你复制对象必须使用复制构造函数时,则为true。如果你可以直接用 memcpy() 函数复制,则为false。当类型为true时为Q_COMPLEX_TYPE。
  •  QTypeInfo<T>::isLarge 这个值等于:sizeof(T) > sizeof(void*)

还有其他属性在类中,当然这些是最重要的。

不看Qt,C++11也引入了type_traits library和提出了is_trivial 和 is_trivially_copyably。在这里,is_trivial 大致对应着 Q_PRIMITIVE_TYPE。但是标准并没有提出对应Q_MOVABLE_TYPE的,即使EASTL <https://github.com/paulhodge/EASTL> 提出了 has_trivial_relocate,但C++11只有is_trivially_copyable,然而它却是个比Q_MOVABLE_TYPE更强大的存在。它指示可以使用 memcpy () 复制这样的类,而 Q_MOVABLE_TYPE/has_trivial_relocate,只是说他们可以在内存中通过memcpy () 迁移。尤其是,所有的引用计数类都是一版重定位的但肯定不是一般地复制。

希望在TR2中C++可以引用is_trivially_relocatable。

 

Size Types

STL的容器一直使用无符号类型(size_t)作为索引位置和大小,然而,Qt容器却是一直使用有符号类型(int)。这就意味着你在Qt和STL容器之间切换的时候你需要经常改变变量类型去避免编译错误和警告。使用嵌套的Container::size_type类型可以使你的代码更优美强大。

 

Associative Container Insertions (关联容器的插入)

一个相当郁闷的差异在STL和Qt之间:STL的唯一关联容器规定所有insert()和其重载在插入前都必须保证这个key在现有容器中不存在才插入成功(当然map[key] = value总是会存储这个value,不管key以前有没有存在)。然而在Qt容器中则相反,insert()总会成功,不管这个key有没有存在,如果存在则替换value的值(除了你使用的是QMulti{Hash,Map})。

就个人而言,我认为STL的行为更一致(insert()永远不会改变当前已经存在key的value,不管是在map还是在multimap中)。但是无论你更倾向于那种做法,你都应该知道这个区别。(注:译者更喜欢Qt的做法)

 

Error Handling(错误处理)

STL元素遍历边界检查规则中使用中括号([])是不进行检查的,使用at()函数越界会抛出std::out_of_range异常。你可以选择你喜欢的检查和处理方式,如果你认为值得和合理的话。在Qt容器中无论你使用哪种遍历方式,中括号([])或者at()函数,都是一个简单的断言错误的。

 

(未完待续、、、)

《[翻译]理解Qt容器:STL VS QTL(三)——类型系统 和其他处理》有10个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注