1

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?

asked Oct 24, 2025 at 15:04
2
  • 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? Commented 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. :-) Commented Oct 28, 2025 at 18:19

1 Answer 1

3

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
answered Oct 28, 2025 at 7:26
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for the great insight into the startup initialization and assembly. I think this only explores part of the potential gains. The other part is when the value is assigned to a QString at run time (something like 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?
I played around with compiler explorer, and much to my disappointment, I didn't see any difference in the generated run-time code between the two approaches (beyond initialization). Perhaps my example was too trivial, or was optimized away? I might have to spend some more time exploring just exactly when QStringLiteral does make a difference.
I think you may have a misunderstood the purpose of 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
To stick with the airport metaphor: the choice between online versus desk check-in becomes more apparent when you do lots of domestic flights: 2 h at the airport + 30 min flight every day quickly becomes unbearable while 2 h airport + 10 h international flight time when you go on vacation once a year won't register quite as much.
If you look at the assembly at the end of my answer, you can find your literal string stored as symbol .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.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.