原做者: 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