原作者: Olivier Goffart 点击打开链接http://woboq.com/blog/qstringliteral.html

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

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

提要

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

大多数情况:

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

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

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

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

QStringLiteral(源代码里有一个通过构造函数产生的从const char*到QString的隐式转换,QStringLiteral字符串可以放在代码的任何地方,编译期直接生成utf16字符串,速度很快,体积变大)的更多相关文章

  1. 大数据学习day17------第三阶段-----scala05------1.Akka RPC通信案例改造和部署在多台机器上 2. 柯里化方法 3. 隐式转换 4 scala的泛型

    1.Akka RPC通信案例改造和部署在多台机器上  1.1 Akka RPC通信案例的改造(主要是把一些参数不写是) Master package com._51doit.akka.rpc impo ...

  2. explicit:C++规定,当定义了只有一个参数的构造函数时,同时也定义了一种隐式的类型转换

    explicit研究   explicit是C++中的关键字,不是C语言中的.英文直译是“明确的”.“显式的”意思.出现这个关键字的原因,是在C++中有这样规定的基础上:当定义了只有一个参数的构造函数 ...

  3. 一个 MySQL 隐式转换的坑,差点把服务器整崩溃了

    我是风筝,公众号「古时的风筝」,专注于 Java技术 及周边生态. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 本来是一个平静而美好的下午,其 ...

  4. 学好Spark/Kafka必须要掌握的Scala技术点(三)高阶函数、方法、柯里化、隐式转换

    5. 高阶函数 Scala中的高阶函数包含:作为值的函数.匿名函数.闭包.柯里化等,可以把函数作为参数传递给方法或函数. 5.1 作为值的函数 定义函数时格式: val 变量名 = (输入参数类型和个 ...

  5. Qt C++中的关键字explicit——防止隐式转换(也就是Java里的装箱),必须写清楚

    最近在复习QT,准备做项目了,QT Creator 默认生成的代码 explicit Dialog(QWidget *parent = 0)中,有这么一个关键字explicit,用来修饰构造函数.以前 ...

  6. Scala基础:闭包、柯里化、隐式转换和隐式参数

    闭包,和js中的闭包一样,返回值依赖于声明在函数外部的一个或多个变量,那么这个函数就是闭包函数. val i: Int = 20 //函数func的方法体中使用了在func外部定义的变量 那func就 ...

  7. C++转换构造函数和隐式转换函数 ~ 转载

    原文地址: C++转换构造函数和隐式转换函数 用转换构造函数可以将一个指定类型的数据转换为类的对象.但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成doubl ...

  8. C++转换构造函数和隐式转换函数

    今天是第一次听到C++还有个转换构造函数,之前经常见到默认构造函数.拷贝构造函数.析构函数,但是从没听说过转换构造函数,隐式转换函数也是一样,C++的确是够博大精深的,再次叹服!          其 ...

  9. C++ 构造函数 隐式转换 深度探索,由‘类对象的赋值操作是否有可能调用到构造函数’该实验现象引发

    Test1 /** Ques: 类对象的赋值操作是否有可能调用到构造函数 ? **/ class mystring { char str[100]; public: mystring() //myst ...

随机推荐

  1. data pump(数据泵)

    先给出oracle给出的一个定义: “Oracle Data Pump technology enables very high-speed movement of data and metadata ...

  2. Reorder the Books(规律)

    Reorder the Books Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Othe ...

  3. 不允许在单例对象中创建Srping容器

    spring.net在使用的时候,不允许在单例对象中创建Srping容器 需要将实例化模式转为单例singleton=“false”

  4. C# DateTime.Now 用法小记

    1.DateTime.Now   获取时间跟系统当前时间一直并且格式一直,如系统时间带有星期几,获取的时间也会带有 2,以下为拷贝前人总结的: //2008年4月24日 System.DateTime ...

  5. java中如何获取系统时间

    需要引入的包有: import java.util.Date; 此为获取当前系统时间,合适为“1991-01-01” String now = "";    SimpleDateF ...

  6. ASP.NET关于Eval的值

    ASP.NET邦定数据“<%#Eval("Sex")%>”运用三元运算符: <%#(Eval(") ? "男" : "女& ...

  7. shell学习-读取输入

    功能:读取输入,打印:如果长度小于MINLEN,那么输出空格. #!/bin/bash # paragraph-space.sh # Insert a blank line between parag ...

  8. 转几篇关于Android webView的网文

    1,控件WebView显示网页 http://www.cnblogs.com/tinyphp/p/3858997.html http://blog.csdn.net/t12x3456/article/ ...

  9. bmfont制作数字

    http://blog.csdn.net/z104207/article/details/20136401

  10. HDU 1870 愚人节的礼物

    题解:简单的数括号问题…… #include <cstdio> char s[1005]; int main(){ while(scanf("%s",s)!=EOF){ ...