Про QStringLiteral
QStringLiteral- це новий макрос, введений у Qt 5, для створення об'єктів QString з рядкових літералів. (Рядкові літерали - це рядки всередині лапок у вихідному коді). У цій статті я поясню, що там усередині та як вони реалізовані.
Дозвольте почати з інформації про те, коли потрібно скористатися макросом. Якщо є необхідність ініціалізувати об'єкт QString з рядкового літералу Qt5, то варто робити так:
- У більшості випадківQStringLiteral(«foo»)якщо він дійсно буде перетворений наQString.
- QLatin1String(«foo») якщо він використовуватиметься в перевантаженихQLatin1Stringметодах. (наприкладoperator==,operator+,startWith,replace, . )
Я навів висновки на початку статті для тих, кому не цікаві технічні деталі.
Згадаймо, як працює QString
QString, як і більшість класів Qt, використовує неявний спільний доступ. Покажчик на “приватні” дані є єдиним членом класу. Пам'ять дляQStringDataвиділяється за допомогою malloc, а потім також виділяється пам'ять для самого рядка.

Дані рядки зберігаються у форматі UTF-16, який використовує по 2 байти на символ.
Літерали та перетворення
Рядкові літерали - це рядки, які зустрічаються прямо в коді в лапках. Наведемо приклад. (action,stringтаfilename— цеQString)
У першому рядку ми викликаємо функціюQObject::setObjectName(const QString&). Відбувається неявне приведення зconst char*уQStringчерез його конструктор. Новий об'єктQStringDataстворюється здостатньою кількістю місця для зберіганняMyObject, а потім рядок копіюється і конвертується з UTF-8 в UTF-16.
Те саме відбувається і в останньому рядку, де викликається функціяQString::replace(const QString &, const QString &). Для"%FileName%"створюється новий об'єктQStringData.
Чи існує рішення, щоб уникнути створення QStringData і копіювання рядка?
Так, одне з рішень для уникнення дорогого створення тимчасового об'єкта QString - це мати перевантажені методи, які приймають параметрамиconst char*. У нас перевантаженийoperator==
Перевантаженим методам не потрібно створювати новий об'єкт QString для нашого літерала. Вони можуть використовувати дані прямо зchar*.
Кодування та QLatin1String
У Qt5 ми змінили кодування рядків за умовчанням зchar*на UTF-8. Але багато алгоритмів набагато повільніше з UTF-8 ніж зі звичайним ASCII або latin1.
Таким чином можна використовуватиQLatin1String, який подібний до тонкої обгортки надchar*і визначає кодування. Перевантажені функції, що приймаютьQLatin1Stringі можуть оперувати, або сиріlatin1дані безпосередньо без перетворення.
Тепер наш перший приклад виглядатиме так:
Хороша новина в тому, що QString::replace іoperator==перевантажені дляQLatin1String. Тепер вони набагато швидші.
У випадку зsetObjectNameми уникли перетворення з UTF-8, але у нас все ж таки залишилося перетворення зQLatin1StringнаQString, яке спричинило створенняQStringDataу купі.
Зустрічайте QStringLiteral
Чи можливо уникнути виділення пам'яті та копіювання рядкового літералу для випадків типуsetObjectName? Так, це те, для чого створеноQStringLiteral.
Макрос генеруватиме об'єктQStringDataпід час компіляції з усіма ініціалізованими полями, який також розміщуватиметься в секції.rodata, тому його можна використовувати між процесами.
Нам потрібні дві особливості мови, для цього:
- Можливість генерувати UTF-16 під час компіляції: У Windows можна використовувати wide charL«String». У Unix ми можемо використовувати новий C++11 Unicode літерал:u«String». (Підтримується GCC 4.4 та clang.)
- Можливість створення статичних даних із виразів. Ми хочемо, щоб була можливість скрізь використовуватиQStringLiteral. Один із шляхів вирішення цієї проблеми - вкластиstatic QStringDataвсередину лямбда виразів С++. (Підтримується MSVC 2010 та GCC 4.5) (Ми так само використовуємо GCC__extension__ (< >))
Реалізація
Нам потрібна POD структура, яка містить якQStringDataтак і самий рядок. Ця структура залежатиме від того, як генерується UTF-16.
Давайте трохи спростимо цей макрос і подивимося, як він розвертатиметься
Лічильник посилань ініціалізований -1. Негативне значення ніколи не збільшується або зменшується, тому що ми в області тільки для читання.
Результати
Заради гри, давайте глянемо на згенерований асемблер дуже простого викликуQStringLiteral. Ми бачимо, що коду практично немає, а дані знаходяться в.rodataсекції.
Не слід забувати про накладні витрати у двійковому вигляді. Рядки займають вдвічі більше пам'яті, оскільки вони закодовані в UTF-16, а так само заголовок sizeof(QStringData) = 24 . Ці накладні витрати і є причиною, черезє сенс використовуватиQLatin1String, коли викликана функція перевантажена.
Скомпільовано g++ -O2 -S -std=c++0x (GCC 4.7) на x86_64
Висновок
Я сподіваюся, що тепер, після прочитання цієї статті, ви матимете більше уявлення, коли варто використовуватиQStringLiteral, а коли ні. Є інший макросQByteArrayLiteral, який працює за тим же принципом, але створюєQByteArray.