When using Qt 5 and 6.
The intent of QStringLiteral is to (effectively) place the QString object into read-only data space and avoid initialization overhead during startup (as described here).
If I want to create a static const QString, is the compiler smart enough to do the same thing, or do I still need to call it explicitly?
// myclass.h
class MyClass
{
static const QString strMyKey;
}
// myclass.cpp
// Does QStringLiteral matter here?
const QString MyClass::strMyKey = QStringLiteral( "keyId" );
// Or will this do the same thing anyway?
const QString MyClass::strMyKey = "keyId";
edit: Based on the first response to the question; let me be clearer. What I'm asking is if there is an efficiency gain between the two approaches? Does using QStringLiteral provide an advantage over a string literal?
-
What kind of efficiency gain would you expect? This is something you can trivially test on your machine. What are the results of your measurements? How do they not match your expectations?Friedrich– Friedrich2025年10月28日 08:41:47 +00:00Commented Oct 28, 2025 at 8:41
-
This is more of an academic question, trying to decide if it's worth the trouble of always typing out the helper instead of just sticking with ordinary strings. I understand scale matters, but this could be an opportunity to establish a good habit before I get to the point where I realize that my scale requires me to go back and fix everything later. :-)Ed K– Ed K2025年10月28日 18:19:32 +00:00Commented Oct 28, 2025 at 18:19
1 Answer 1
Compilers are great in simplifying code you've written, and choosing correct function overloads. However, they (削除) can not (削除ここまで) shall not magically make decisions for you.
It does not know, that your const char* "keyId" can, in fact, be internal data for QString.
But don't take my word for it, you can see it for yourself with compiler explorer.
I took your code sample and compiled it once for each type definition (full output below).
Notice, how for the char* variant the QString::QString(char const*) constructor gets linked
lea rax, .LC10[rip]
mov rdx, QWORD PTR MyClass::strMyKey@GOTPCREL[rip]
mov rsi, rax
mov rdi, rdx
call QString::QString(char const*)@PLT
whereas in the QStringLiteral variant QString::QString(QArrayDataPointer<char16_t>&&) is linked instead:
lea rdx, .LC7[rip]
mov QWORD PTR -8[rbp], rdx
mov rdx, QWORD PTR -8[rbp]
mov ecx, 5
mov esi, 0
mov rdi, rax
call QArrayDataPointer<char16_t>::QArrayDataPointer(QTypedArrayData<char16_t>*, char16_t*, long long)@PLT
lea rax, -32[rbp]
mov rdx, QWORD PTR MyClass::strMyKey@GOTPCREL[rip]
mov rsi, rax
mov rdi, rdx
call QString::QString(QArrayDataPointer<char16_t>&&)@PLT
That being said, since static const initialization happens only once during startup, and the performance increase will be very slim, you likely won't notice any difference if you don't have thousands of these const variables in your code. So as long as there is no specific measurement or reason, I would not go down this rabbit hole too far.
As for runtime performance, it is trivially easy to write a quick benchmark using QTest:
void benchmarkStringLiteral() {
QBENCHMARK {
const QString s = QStringLiteral("Hello World");
}
QBENCHMARK {
const QString s = "Hello World";
}
QBENCHMARK {
QString s = QStringLiteral("Hello World");
}
QBENCHMARK {
QString s = "Hello World";
}
}
The output is pretty self-explanatory:
RESULT : Main::benchmarkStringLiteral():
0.0000061 msecs per iteration (total: 13, iterations: 2097152)
0.0000839 msecs per iteration (total: 176, iterations: 2097152)
0.0000061 msecs per iteration (total: 26, iterations: 4194304)
0.0000882 msecs per iteration (total: 370, iterations: 4194304)
You can see, that string creation using QStringLiteral is roughly 10-15 times faster than plain const char* creation. This is logical, since for the later, QString has to read the text and run its utf8 conversion logic on it, whereas with QStringLiteral, we just reference plain data as-is.
You can see, that in absolute terms the difference is marginal (0.0000075 seconds), but if you create a LOT of strings, QStringLiteral should be a definite goto.
Here is the full generated assembly code for x86-64 gcc 15.2 (but others will look similar):
const char* variant
.set QString::QString(char const*) [complete object constructor],_ZN7QStringC2EPKc
.set QString::~QString() [complete object destructor],_ZN7QStringD2Ev
MyClass::strMyKey:
.zero 24
.LC0:
.string "/app/qt/include/QtCore/qbytearrayview.h"
.LC1:
.string "len >= 0"
.LC2:
.string "data || !len"
.set QByteArrayView::QByteArrayView<char, true>(char const*, long long),_ZN14QByteArrayViewC2IcLb1EEEPKT_x
.set QByteArrayView::QByteArrayView<char const*, true>(char const* const&),_ZN14QByteArrayViewC2IPKcLb1EEERKT_
.set QArrayDataPointer<char16_t>::~QArrayDataPointer() [complete object destructor],_ZN17QArrayDataPointerIDsED2Ev
.LC3:
.string "__b != memory_order_release"
.LC4:
.string "std::__atomic_base<_IntTp>::__int_type std::__atomic_base<_IntTp>::load(std::memory_order) const [with _ITp = int; __int_type = int]"
.LC5:
.string "/cefs/22/22e6cdc013c8541ce3d1548e_consolidated/compilers_c++_x86_gcc_15.2.0/include/c++/15.2.0/bits/atomic_base.h"
.LC6:
.string "__b != memory_order_acq_rel"
.LC7:
.string "/app/qt/include/QtCore/qarraydataops.h"
.LC8:
.string "this->d"
.LC9:
.string "this->d->ref_.loadRelaxed() == 0"
.LC10:
.string "keyId"
__static_initialization_and_destruction_0():
push rbp
mov rbp, rsp
lea rax, .LC10[rip]
mov rdx, QWORD PTR MyClass::strMyKey@GOTPCREL[rip]
mov rsi, rax
mov rdi, rdx
call QString::QString(char const*)@PLT
lea rax, __dso_handle[rip]
mov rdx, QWORD PTR MyClass::strMyKey@GOTPCREL[rip]
mov rsi, rdx
mov rdx, QWORD PTR QString::~QString()@GOTPCREL[rip]
mov rcx, rdx
mov rdx, rax
mov rdi, rcx
call __cxa_atexit@PLT
nop
pop rbp
ret
_GLOBAL__sub_I_example.cpp:
push rbp
mov rbp, rsp
call __static_initialization_and_destruction_0()
pop rbp
ret
QStringLiteral() variant
.set QString::QString(QArrayDataPointer<char16_t>&&) [complete object constructor],_ZN7QStringC2EO17QArrayDataPointerIDsE
.set QString::~QString() [complete object destructor],_ZN7QStringD2Ev
MyClass::strMyKey:
.zero 24
.set QArrayDataPointer<char16_t>::QArrayDataPointer(QArrayDataPointer<char16_t>&&) [complete object constructor],_ZN17QArrayDataPointerIDsEC2EOS0_
.set QArrayDataPointer<char16_t>::~QArrayDataPointer() [complete object destructor],_ZN17QArrayDataPointerIDsED2Ev
.set QArrayDataPointer<char16_t>::QArrayDataPointer(QTypedArrayData<char16_t>*, char16_t*, long long) [complete object constructor],_ZN17QArrayDataPointerIDsEC2EP15QTypedArrayDataIDsEPDsx
.LC0:
.string "__b != memory_order_release"
.LC1:
.string "std::__atomic_base<_IntTp>::__int_type std::__atomic_base<_IntTp>::load(std::memory_order) const [with _ITp = int; __int_type = int]"
.LC2:
.string "/cefs/22/22e6cdc013c8541ce3d1548e_consolidated/compilers_c++_x86_gcc_15.2.0/include/c++/15.2.0/bits/atomic_base.h"
.LC3:
.string "__b != memory_order_acq_rel"
.LC4:
.string "/app/qt/include/QtCore/qarraydataops.h"
.LC5:
.string "this->d"
.LC6:
.string "this->d->ref_.loadRelaxed() == 0"
.LC7:
.string "k"
.string "e"
.string "y"
.string "I"
.string "d"
.zero 2
__static_initialization_and_destruction_0():
push rbp
mov rbp, rsp
sub rsp, 32
lea rax, -32[rbp]
lea rdx, .LC7[rip]
mov QWORD PTR -8[rbp], rdx
mov rdx, QWORD PTR -8[rbp]
mov ecx, 5
mov esi, 0
mov rdi, rax
call QArrayDataPointer<char16_t>::QArrayDataPointer(QTypedArrayData<char16_t>*, char16_t*, long long)@PLT
lea rax, -32[rbp]
mov rdx, QWORD PTR MyClass::strMyKey@GOTPCREL[rip]
mov rsi, rax
mov rdi, rdx
call QString::QString(QArrayDataPointer<char16_t>&&)@PLT
lea rax, -32[rbp]
mov rdi, rax
call QArrayDataPointer<char16_t>::~QArrayDataPointer()@PLT
lea rax, __dso_handle[rip]
mov rdx, QWORD PTR MyClass::strMyKey@GOTPCREL[rip]
mov rsi, rdx
mov rdx, QWORD PTR QString::~QString()@GOTPCREL[rip]
mov rcx, rdx
mov rdx, rax
mov rdi, rcx
call __cxa_atexit@PLT
nop
leave
ret
_GLOBAL__sub_I_example.cpp:
push rbp
mov rbp, rsp
call __static_initialization_and_destruction_0()
pop rbp
ret
5 Comments
QString copy = strMyKey;) . The Qt doc explains that this is where the real benefit happens, so I wonder if that's visible between the two options? Am I correct, in interpreting the assembly, that more of the object is placed into the data section to "thunk" when a new QString copy is created rather than running through a normal copy?QStringLiteral does make a difference.QStringLiteral. It only changes the initialization of a QString. After that, QStrings created with both variants are virtually indistinguishable from another. You wouln't see any difference at all in code. An analogy may help: If you want to fly to some other country, you can go to the airport, buy a ticket at the counter, go to the check-in desk etc. - or you can buy the ticket and check-in online before going to the airport. The flight will take you the same time, but in one case you spent way less time at the terminal.LC10 / .LC7. This is the actual character data that your application will always need to store somewhere (if you run linux, try to execute strings _some_executable_, you might be surprised what you see). However, you may notice, that the QStringLiteral approach stores a lot more stuff that is readily compiled in your application. These are the pre-created QArrayDataPointers, that are used to instantiate your QStrings at runtime. This is what saves you performance.