QStringLiteral(源代码里有一个经过构造函数产生的从const char*到QString的隐式转换,QStringLiteral字符串能够放在代码的任何地方,编译期直接生成utf16字符

         原做者: Olivier Goffart 点击打开连接http://woboq.com/blog/qstringliteral.htmlhtml

         译者: zzjin 点击打开连接http://www.tuicool.com/articles/6nUrIr                   linux

QStringLieral是Qt5中新引入的一个用来从“字符串常量”建立QString对象的宏(字符串常量指在源码中由双引号包含的字符串)。在这篇博客我讲解释它的的内部实现和工做原理。c++

 

提要算法

让咱们从它的使用环境开始提及:假设你想要在Qt5中从字符串常量初始化一个QString对象,你应该这样:函数

大多数状况:ui

(1)使用QStringLiteral(“某字符串”) --若是它最终转会换成QString的话this

(2)使用QLatin1String(“某字符串”) --若是使用的函数有支持QLatin1String的重载(好比operator==, operator+, startWith, replace等)的话编码

我把这段话放在最开始是为了那些不怎么想了解其具体技术细节的人着想。继续阅读你将了解QStringLiteral是如何工做的。spa

 

QString的工做方式操作系统

QString和Qt中的其余类同样,是一个”隐式共享类“。它惟一的数据成员就是一个指向其“私有”数据的指针。QStringData由malloc函数分配空间,而且在其后(同一块内存块)分配了足够的空间来存放实际的字符数据。

//为了此博客的目标作了简化

struct QStringData {

   QtPrivate::RefCount ref; // 对QAtomicInt进行封装

   int size; // 字符串的大小

   uint alloc : 31 ; // 该字符串数据以后预留的内存数

   uint capacityReserved : 1 ; // reserve()使用到的内部细节

   qptrdiff offset; // 数据的偏移量 (一般是 sizeof(QStringData))

   inline ushort *data()

   { return reinterpret_cast < ushort *>( reinterpret_cast <char *>( this ) + offset); }

};

// ...

class QString {

   QStringData *d;

public :

   // ... 公共 API ...

};

 

offset是指向QStringData相对数据的指针。在Qt4中它是一个实际的指针。稍后咱们会讲到为何这个指针发生了变化。在字符串中保存的实际数据是UTF-16编码的,这意味着每个字符都占用了两个字节。

 

文字与转换

字符串常量是指直接在源码中用引号包起来的字符串。 这有一些例子。(假设action,string和filename都是QString类型)

o->setObjectName( "MyObject" );

if (action == "rename" )

string.replace( "%FileName%" , filename);

第一行咱们调用了QObject::setObjectName(const QString&)函数。这里有一个经过构造函数产生的从const char*到QString的隐式转换。一个新的QStringData获取了足够保存 "MyObject"字符串的空间,接着这个字符串 从 UTF-8转码为UTF-16并拷贝到Data内 。 
在最后一行调用QString::replace(const QString &, const QString &)函数的时候也发生了相同的操做,一个新的QStringData获取了保存 "%FileName%"的空间。


有办法避免QStringData的内存分配和字符串的复制操做吗?
固然有,建立临时的QString对象耗费甚巨,解决这个问题的一个方法是重载一个const char*做为参数的通用方法。 因而 咱们有了下面的这几个赋值运算符重载:

bool operator==( const QString &, const QString &);

bool operator==( const QString &, const char *);

bool operator==( const char *, const QString &)

这些重载运算能够直接操做原始char*,没必要为了咱们的字符串常量去建立临时QString对象。

 

编码与 QLatin1String

在Qt5中,咱们把char* 字符串的默认编码 改为了UTF-8。可是相对纯ASCII或者latin1而言,不少算法处理UTF-8编码数据的时候会慢不少。

所以你可使用QLatin1String,它是在肯定编码的状况下对char*进行的轻量级封装。一些接收QLatin1String为参数的重载函数可以直接对纯latin1数据进行处理,没必要进行编码转换。

因此咱们的第一个例子如今看起来是这样了:

o->setObjectName( QLatin1String ( "MyObject" ));

if (action == QLatin1String ( "rename" ))

     string.replace( QLatin1String ( "%FileName%" ), filename);

好消息是QString::replace与operator==操做有了针对QLatin1String的重载函数,因此如今快不少。

在对setObjectName的调用中,咱们避免了从UTF-8的编码转换,可是咱们仍然须要进行一次从QLatin1String到QString的(隐性)转换, 因此不得不堆中分配QStringData的空间。

 

QStringLiteral

有没有可能在调用setObjectName的时候同时阻止分配空间与复制字符串常量呢?固然,这就是 QStringLiteral所作的。

这个宏会在编译时尝试生成QStringData,并初始化其所有字段。它甚至是存放在.rodata内存段 中因此能够在不一样的进程中共享。

为了实现这个目标咱们须要两个C++语言的特性:

在编译的时候生成UTF-16格式字符串的可能性 
Win环境下咱们可使用宽字符 L"String"。 Unix环境下咱们使用新的C++11 Unicode字符串: u"String"。( GCC 4.4和clang支持。)

从表达式中建立静态数据的能力 
咱们但愿能把QStringLiteral放在代码的任何地方。一种实现方法就是把一个静态的QStringData放入一个C++11 lambda 表达式。(MSVC 2010和GCC 4.5支持) (咱们一样用到了GCC  __extension__ ({ })   )

实现

咱们须要一个同时包含了QStringData和实际字符串的POD结构。这个结构取决于咱们生成的UTF-16时使用的实现方法。

定义QT_UNICODE_LITERAL_II而且声明基于编译器的qunicodechar  */

#if defined(Q_COMPILER_UNICODE_STRINGS)

    // C++11 unicode 字符串

    #define QT_UNICODE_LITERAL_II(str) u"" str

    typedef char16_t qunicodechar;

#elif __SIZEOF_WCHAR_T__ == 2

    // wchar_t是两个字节  (这里条件被适当简化)

    #define QT_UNICODE_LITERAL_II(str) L##str

    typedef wchar_t qunicodechar;

#else

    typedef ushort qunicodechar; //fallback

#endif

// 会包含字符串的结构体

// N是字符串大小

template < int N>

struct QStaticStringData

{

     QStringData str;

     qunicodechar data[N + 1 ];

};

// 包裹了指针的辅助类使得咱们能够将其传递给QString的构造函数

struct QStringDataPtr

{ QStringData *ptr; };

if defined(QT_UNICODE_LITERAL_II)

// QT_UNICODE_LITERAL needed because of macro expension rules

# define QT_UNICODE_LITERAL(str) QT_UNICODE_LITERAL_II(str)

# if defined(Q_COMPILER_LAMBDA)

#  define QStringLiteral(str) \

     ([]() ->  QString   { \

         enum   { Size =  sizeof ( QT_UNICODE_LITERAL (str))/ 2  -  1   }; \

         static   const   QStaticStringData<Size> qstring_literal = { \

             Q_STATIC_STRING_DATA_HEADER_INITIALIZER(Size), \

             QT_UNICODE_LITERAL (str) }; \

         QStringDataPtr   holder = { &qstring_literal.str }; \

         const   QString   s(holder); \

         return   s; \

     }()) \

# elif defined(Q_CC_GNU)

// 使用GCC的 __extension__ ({ }) 技巧代替lambda

// ... <skiped> ...

# endif

#endif

#ifndef QStringLiteral

// 不支持lambdas, 不是GCC,或者GCC为C++98模式,使用4字节wchar_t

// fallback, 返回一个临时的QString

// 默认认为源码为utf-8编码

# define QStringLiteral(str) QString::fromUtf8(str, sizeof(str) - 1)

#endif

 

让咱们稍微简化一下这个宏,而后看看这个宏是如何展开的

o->setObjectName( QStringLiteral ( "MyObject" ));

// 将展开为:

o->setObjectName(([]() {

         // 咱们在一个返回QStaticString的lambda表达式中

         // 使用sizeof计算大小(去掉末尾的零结束符)

         enum { Size = sizeof (u "MyObject" )/ 2 - 1 };

         // 初始化(静态数据在编译时初始化)

         static const QStaticStringData <Size> qstring_literal =

         { { /* ref = */ - 1 ,

             /* size = */ Size,

             /* alloc = */ 0 ,

             /* capacityReserved = */ 0 ,

             /* offset = */ sizeof ( QStringData ) },

           u "MyObject" };

          QStringDataPtr holder = { &qstring_literal.str };

          QString s(holder);// 调用QString(QStringDataPtr&)构造函数

          return s;

     }()) // 调用lambda

   );

引用计数器初始化为-1。因为这是只读数据因此这个负数永远不会发生增减。

能够看到,咱们使用一个偏移量(qptrdiff)而不是向Qt4中那样使用一个指向字符串的指针是多么重要。把一个指针放在一个只读的部分里面是彻底不可能的,由于指针极可能会在加载时 从新分配 。这意味着每次启动或者调用程序、库文件的时候操做系统都不得不用重分配表重写所有的指针地址。

数据结果

为了好玩,咱们来看一段从一个很是简单的对QStringLiteral的调用后生成的汇编代码。 能够看到下面几乎没有什么代码,还有.rodata段的数据分布。

QString returnAString() {

     return QStringLiteral ( "Hello" );

}

在x84_64用g++ -O2 -S -std=c++0x (GCC 4.7)编译后

. text

     . globl   _Z13returnAStringv

     . type    _Z13returnAStringv, @function

_Z13returnAStringv:

     ; load the address of the QStringData into %rdx

     leaq    _ZZZ13returnAStringvENKUlvE_clEvE15qstring_literal(%rip), %rdx

     movq     %rdi, %rax

     ; copy the QStringData from %rdx to the QString return object

    ; allocated by the caller.  (the QString constructor has been inlined)

     movq     %rdx, (%rdi)

     ret

     . size    _Z13returnAStringv, .-_Z13returnAStringv

     . section     .rodata

     . align 32

     . type   _ZZZ13returnAStringvENKUlvE_clEvE15qstring_literal, @object

     . size    _ZZZ13returnAStringvENKUlvE_clEvE15qstring_literal, 40

_ZZZ13returnAStringvENKUlvE_clEvE15qstring_literal:

     . long    - 1    ; ref

     . long    5     ; size

     . long    0     ; alloc + capacityReserved

     . zero    4     ; padding

     . quad    24    ; offset

     . string "H"   ; the data. Each .string add a terminal ''

     . string "e"

     . string "l"

     . string "l"

     . string "o"

     . string ""

     . string ""

     . zero    4

 

结论

我但愿读完这篇博客的如今,大家能更好的理解何时用和不用QStringLiteral。 
还有一个宏叫作QByteArrayLiteral,工做原理和QStringLiteral几乎如出一辙可是建立的是QByteArray。

http://blog.csdn.net/zyx_linux/article/details/23696375

相关文章
相关标签/搜索