Rewrite Q_{GLOBAL,APPLICATION}_STATIC with C++17 goodies

Especially static inline variables. This greatly reduces the amount of
code that existed in macros, moving them to templates.

Additionally, this removes one level of indirection from
Q_APPLICATION_STATIC by removing the std::unique_ptr. We now directly
manage the object's storage.

Change-Id: I2cffe62afda945079b63fffd16bcc825cc04334e
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
This commit is contained in:
Thiago Macieira 2021-12-01 16:20:29 -08:00
parent fbbcd109f5
commit 81a31beeb2
5 changed files with 205 additions and 162 deletions

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 Intel Corporation.
** Copyright (C) 2021 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
@ -44,6 +44,7 @@
#include <QtCore/qatomic.h>
#include <atomic> // for bootstrapped (no thread) builds
#include <type_traits>
QT_BEGIN_NAMESPACE
@ -55,92 +56,92 @@ enum GuardValues {
Uninitialized = 0,
Initializing = 1
};
}
#define Q_GLOBAL_STATIC_INTERNAL_HOLDER(ARGS) \
struct HolderBase \
{ \
HolderBase() = default; \
~HolderBase() noexcept \
{ \
if (guard.loadRelaxed() == QtGlobalStatic::Initialized) \
guard.storeRelaxed(QtGlobalStatic::Destroyed); \
} \
Q_DISABLE_COPY_MOVE(HolderBase) \
}; \
struct Holder : public HolderBase \
{ \
Type value; \
Holder() noexcept(noexcept(typename std::remove_cv<Type>::type ARGS)) \
: value ARGS \
{ \
guard.storeRelaxed(QtGlobalStatic::Initialized); \
} \
};
template <typename QGS> struct Holder
{
using Type = typename QGS::QGS_Type;
using PlainType = std::remove_cv_t<Type>;
#if defined(Q_OS_UNIX) && defined(Q_CC_INTEL)
// Work around Intel issue ID 6000058488:
// local statics inside an inline function inside an anonymous namespace are global
// symbols (this affects the IA-64 C++ ABI, so OS X and Linux only)
# define Q_GLOBAL_STATIC_INTERNAL_DECORATION Q_DECL_HIDDEN
#else
# define Q_GLOBAL_STATIC_INTERNAL_DECORATION Q_DECL_HIDDEN inline
#endif
static constexpr bool ConstructionIsNoexcept = noexcept(QGS::innerFunction(nullptr));
std::aligned_union_t<1, PlainType> storage;
static inline QBasicAtomicInteger<qint8> guard = { QtGlobalStatic::Uninitialized };
#define Q_GLOBAL_STATIC_INTERNAL(ARGS) \
Q_GLOBAL_STATIC_INTERNAL_DECORATION Type *innerFunction() \
{ \
Q_GLOBAL_STATIC_INTERNAL_HOLDER(ARGS) \
static Holder holder; \
return &holder.value; \
Holder() noexcept(ConstructionIsNoexcept)
{
QGS::innerFunction(pointer());
guard.storeRelaxed(QtGlobalStatic::Initialized);
}
// this class must be POD, unless the compiler supports thread-safe statics
template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard>
struct QGlobalStatic
{
typedef T Type;
~Holder()
{
guard.storeRelaxed(QtGlobalStatic::Destroyed);
std::atomic_thread_fence(std::memory_order_acquire); // avoid mixing stores to guard and *pointer()
pointer()->~PlainType();
}
bool isDestroyed() const { return guard.loadRelaxed() <= QtGlobalStatic::Destroyed; }
bool exists() const { return guard.loadRelaxed() == QtGlobalStatic::Initialized; }
PlainType *pointer() noexcept
{
return reinterpret_cast<PlainType *>(&storage);
}
Q_DISABLE_COPY_MOVE(Holder)
};
}
template <typename Holder> struct QGlobalStatic
{
using Type = typename Holder::Type;
bool isDestroyed() const { return guardValue() <= QtGlobalStatic::Destroyed; }
bool exists() const { return guardValue() == QtGlobalStatic::Initialized; }
operator Type *()
{
if (isDestroyed())
return nullptr;
return innerFunction();
return instance();
}
Type *operator()()
{
if (isDestroyed())
return nullptr;
return innerFunction();
return instance();
}
Type *operator->()
{
Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC",
"The global static was used after being destroyed");
return innerFunction();
return instance();
}
Type &operator*()
{
Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC",
"The global static was used after being destroyed");
return *innerFunction();
return *instance();
}
protected:
static Type *instance() noexcept(Holder::ConstructionIsNoexcept)
{
static Holder holder;
return holder.pointer();
}
static QtGlobalStatic::GuardValues guardValue()
{
return QtGlobalStatic::GuardValues(Holder::guard.loadAcquire());
}
};
#define Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \
QT_WARNING_PUSH \
QT_WARNING_DISABLE_CLANG("-Wunevaluated-expression") \
namespace { namespace Q_QGS_ ## NAME { \
typedef TYPE Type; \
QBasicAtomicInt guard = Q_BASIC_ATOMIC_INITIALIZER(QtGlobalStatic::Uninitialized); \
Q_GLOBAL_STATIC_INTERNAL(ARGS) \
} } \
static QGlobalStatic<TYPE, \
Q_QGS_ ## NAME::innerFunction, \
Q_QGS_ ## NAME::guard> NAME; \
QT_WARNING_POP
namespace { struct Q_QGS_ ## NAME { \
typedef TYPE QGS_Type; \
static void innerFunction(void *pointer) \
noexcept(noexcept(std::remove_cv_t<QGS_Type> ARGS)) \
{ \
new (pointer) QGS_Type ARGS; \
} \
}; } \
static QGlobalStatic<QtGlobalStatic::Holder<Q_QGS_ ## NAME>> NAME; \
/**/
#define Q_GLOBAL_STATIC(TYPE, NAME) \
Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ())

View File

@ -1,6 +1,6 @@
/****************************************************************************
**
** Copyright (C) 2016 Intel Corporation.
** Copyright (C) 2021 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the Qt Toolkit.
@ -195,11 +195,11 @@
\omit
\section1 Compatibility with Qt 4 and Qt 5.0
This macro, in its current form and behavior, was introduced in Qt 5.1.
Prior to that version, Qt had another macro with the same name that was
private API. This section is not meant to document how to use
Q_GLOBAL_STATIC in those versions, but instead to serve as a porting guide
for Qt code that used those macros.
This macro, in its current behavior, was introduced in Qt 5.1. Prior to
that version, Qt had another macro with the same name that was private API.
This section is not meant to document how to use Q_GLOBAL_STATIC in those
versions, but instead to serve as a porting guide for Qt code that used
those macros.
The Qt 4 Q_GLOBAL_STATIC macro differed in behavior in the following ways:
@ -218,24 +218,38 @@
\section1 Implementation Details
Q_GLOBAL_STATIC is implemented by creating a QBasicAtomicInt called the \c
guard and a free, inline function called \c innerFunction. The guard
variable is initialized to value 0 (chosen so that the guard can be placed
in the .bss section of the binary file), which denotes that construction
has not yet taken place (uninitialized). The inner function is implemented
by the helper macro Q_GLOBAL_STATIC_INTERNAL.
Q_GLOBAL_STATIC is implemented by creating a type called \c Q_QGS_NAME
where \c NAME is the name of the variable the user passed to the macro,
inside an unnamed namespace, and a \c static variable of \l QGlobalStatic
type, named \c NAME. The use of unnamed namespaces forces the compiler to emit
non-exported symbols, often local to the translation unit, and this
propagates to the \l QGlobalStatic template instantiation that uses such
types. Additionally, because the type is used only for one variable,
there's effectively no difference between static and non-static data
members.
Both the guard variable and the inner function are passed as template
parameters to QGlobalStatic, along with the type \a Type. Both should also
have static linkage or be placed inside an anonymous namespace, so that the
visibility of Q_GLOBAL_STATIC is that of a global static. To permit
multiple Q_GLOBAL_STATIC per translation unit, the guard variable and the
inner function must have unique names, which can be accomplished by
concatenating with \a VariableName or by placing them in a namespace that
has \a VariableName as part of the name. To simplify and improve
readability on Q_GLOBAL_STATIC_INTERNAL, we chose the namespace solution.
It's also required for C++98 builds, since static symbols cannot be used as
template parameters.
The "QGS" type is a \c struct containing a \c typedef to \a TYPE and a
static member function that receives a pointer to storage suitable to hold
an instance of \a TYPE. It will initialize the storage using a placement \c
new and the variadic arguments.
The majority of the work is done by the public \l QGlobalStatic class and
the private \c QtGlobalStatic::Holder class. The \c Holder class has a
non-static, trivial member of suitable size and alignment to hold \a TYPE
(a \c{std::aligned_union_t} or a \c{std::aligned_storage_t}). The
constructor calls the "QGS" type's static member function with a pointer to
this location so it can be initialized and the destructor calls the type's
destructor. The \c{Holder} type is therefore neither trivially
constructible nor trivially destructible. It is used as a function-local \c
static so its initialization is thread-safe due to C++11's requirement that
such variables be thread-safely initialized.
Additionally, both the constructor and destructor modify a guard variable
after construction and before destruction, respectively. The guard variable
is implemented as a \c {static inline} member instead of a non-static
member so the compiler and linker are free to place this variable in memory
far from the actual object. This way, if we wanted to, we could mark it
aligned-to-cacheline in the future to prevent false sharing.
The guard variable can assume the following values:
@ -257,34 +271,6 @@
operate solely on the guard variable: the former returns \c true if the guard
is negative, whereas the latter returns \c true only if it is -2.
The Q_GLOBAL_STATIC_INTERNAL macro implements the actual construction and
destruction. There are two implementations of it: one for compilers that
support thread-safe initialization of function-local statics and one for
compilers that don't. Thread-safe initialization is required by C++11 in
[stmt.decl], but as of the time of this writing, only compilers based on
the IA-64 C++ ABI implemented it properly. The implementation requiring
thread-safe initialization is also used on the Qt bootstrapped tools, which
disable the "thread" feature.
The implementation requiring thread-safe initialization from the compiler
is the simplest: it creates the \a Type object as a function-local static
and returns its address. The actual object is actually inside a holder
structure so holder's destructor can set the guard variable to the value -2
(destroyed) when the type has finished destruction. Since we need to set
the guard \b after the destruction has finished, this code needs to be in a
base struct's destructor. And it only sets to -2 (destroyed) if it finds
the guard at -1 (initialized): this is done to ensure that the guard isn't
set to -2 in the event the type's constructor threw an exception. A holder
structure is used to avoid creating two statics, which the ABI might
require duplicating the thread-safe control structures for.
The other implementation is similar to Qt 4's Q_GLOBAL_STATIC, but unlike
that one, it uses a \l QBasicMutex to provide locking. It is also more
efficient memory-wise. It use a simple double-checked locking of the mutex
and then creates the contents on the heap. After that, it creates a
function-local structure called "Cleanup", whose destructor will be run at
program exit and will actually destroy the contents.
\endomit
\sa Q_GLOBAL_STATIC_WITH_ARGS(), QGlobalStatic
@ -356,7 +342,7 @@
*/
/*!
\fn template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard> bool QGlobalStatic<T, innerFunction, guard>::isDestroyed() const
\fn template <typename Holder> bool QGlobalStatic<Holder>::isDestroyed() const
This function returns \c true if the global static object has already
completed destruction (that is, if the destructor for the type has already
@ -386,7 +372,7 @@
*/
/*!
\fn template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard> bool QGlobalStatic<T, innerFunction, guard>::exists() const
\fn template <typename Holder> bool QGlobalStatic<Holder>::exists() const
This function returns \c true if the global static object has already
completed initialization (that is, if the constructor for the type has
@ -436,7 +422,7 @@
/*!
\keyword qglobalstatic-operator-type-ptr
\fn template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard> QGlobalStatic<T, innerFunction, guard>::operator Type*()
\fn template <typename Holder> QGlobalStatic<Holder>::operator Type*()
This function returns the address of the contents of this global static. If
the contents have not yet been created, they will be created thread-safely
@ -469,7 +455,7 @@
*/
/*!
\fn template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard> Type *QGlobalStatic<T, innerFunction, guard>::operator()()
\fn template <typename Holder> Type *QGlobalStatic<Holder>::operator()()
\deprecated
This function returns the address of the contents of this global static. If
@ -485,7 +471,7 @@
*/
/*!
\fn template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard> Type *QGlobalStatic<T, innerFunction, guard>::operator->()
\fn template <typename Holder> Type *QGlobalStatic<Holder>::operator->()
This function returns the address of the contents of this global static. If
the contents have not yet been created, they will be created thread-safely
@ -498,7 +484,7 @@
*/
/*!
\fn template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard> Type &QGlobalStatic<T, innerFunction, guard>::operator*()
\fn template <typename Holder> Type &QGlobalStatic<Holder>::operator*()
This function returns a reference to the contents of this global static. If
the contents have not yet been created, they will be created thread-safely

View File

@ -1,6 +1,7 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Copyright (C) 2021 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
@ -41,58 +42,75 @@
#define QAPPLICATIONSTATIC_H
#include <QtCore/QMutex>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qglobalstatic.h>
QT_BEGIN_NAMESPACE
#define Q_APPLICATION_STATIC_INTERNAL_HOLDER(ARGS) \
Q_GLOBAL_STATIC_INTERNAL_HOLDER(ARGS) \
struct LifecycleHolder : public Holder \
{ \
LifecycleHolder() { connectLifecycle(); } \
BaseType *get() \
{ \
const QMutexLocker locker(&theMutex); \
if (value.get() == nullptr \
&& guard.loadRelaxed() == QtGlobalStatic::Initialized) { \
value.reset(ARGS); \
connectLifecycle(); \
} \
return value.get(); \
} \
void connectLifecycle() \
{ \
Q_ASSERT(QCoreApplication::instance()); \
QObject::connect(QCoreApplication::instance(), \
&QCoreApplication::destroyed, [this] { \
const QMutexLocker locker(&theMutex); \
value.reset(nullptr); \
}); \
} \
};
namespace QtGlobalStatic {
template <typename QAS> struct ApplicationHolder
{
using Type = typename QAS::QAS_Type;
using PlainType = std::remove_cv_t<Type>;
#define Q_APPLICATION_STATIC_INTERNAL(ARGS) \
Q_GLOBAL_STATIC_INTERNAL_DECORATION BaseType *innerFunction() \
{ \
Q_APPLICATION_STATIC_INTERNAL_HOLDER(ARGS) \
static LifecycleHolder holder; \
return holder.get(); \
static inline std::aligned_union_t<1, PlainType> storage;
static inline QBasicAtomicInteger<qint8> guard = { QtGlobalStatic::Uninitialized };
static inline QBasicMutex mutex {};
static constexpr bool MutexLockIsNoexcept = noexcept(mutex.lock());
static constexpr bool ConstructionIsNoexcept = noexcept(QAS::innerFunction(nullptr));
ApplicationHolder() = default;
Q_DISABLE_COPY_MOVE(ApplicationHolder)
~ApplicationHolder()
{
if (guard.loadRelaxed() == QtGlobalStatic::Initialized) {
guard.storeRelease(QtGlobalStatic::Destroyed);
realPointer()->~PlainType();
}
}
static PlainType *realPointer()
{
return reinterpret_cast<PlainType *>(&storage);
}
// called from QGlobalStatic::instance()
PlainType *pointer() noexcept(MutexLockIsNoexcept && ConstructionIsNoexcept)
{
if (guard.loadRelaxed() == QtGlobalStatic::Initialized)
return realPointer();
QMutexLocker locker(&mutex);
if (guard.loadRelaxed() == QtGlobalStatic::Uninitialized) {
QAS::innerFunction(realPointer());
QObject::connect(QCoreApplication::instance(), &QObject::destroyed, reset);
guard.storeRelaxed(QtGlobalStatic::Initialized);
}
return realPointer();
}
static void reset()
{
if (guard.loadRelaxed() == QtGlobalStatic::Initialized) {
QMutexLocker locker(&mutex);
realPointer()->~PlainType();
guard.storeRelaxed(QtGlobalStatic::Uninitialized);
}
}
};
} // namespace QtGlobalStatic
#define Q_APPLICATION_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \
QT_WARNING_PUSH \
QT_WARNING_DISABLE_CLANG("-Wunevaluated-expression") \
namespace { namespace Q_QAS_ ## NAME { \
typedef TYPE BaseType; \
typedef std::unique_ptr<BaseType> Type; \
QBasicAtomicInt guard = Q_BASIC_ATOMIC_INITIALIZER(QtGlobalStatic::Uninitialized); \
QBasicMutex theMutex; \
Q_APPLICATION_STATIC_INTERNAL((new BaseType ARGS)) \
} } \
static QGlobalStatic<TYPE, \
Q_QAS_ ## NAME::innerFunction, \
Q_QAS_ ## NAME::guard> NAME; \
QT_WARNING_POP
namespace { struct Q_QAS_ ## NAME { \
typedef TYPE QAS_Type; \
static void innerFunction(void *pointer) \
noexcept(noexcept(std::remove_cv_t<QAS_Type> ARGS)) \
{ \
new (pointer) QAS_Type ARGS; \
} \
}; } \
static QGlobalStatic<QtGlobalStatic::ApplicationHolder<Q_QAS_ ## NAME>> NAME;\
/**/
#define Q_APPLICATION_STATIC(TYPE, NAME) \
Q_APPLICATION_STATIC_WITH_ARGS(TYPE, NAME, ())

View File

@ -47,8 +47,10 @@
type will be recreated when it's accessed again once a new QCoreApplication
has been created.
Since the value is bound to the QCoreApplication it should only ever be accessed,
if there is a valid QCoreApplication::instance().
Since the value is bound to the QCoreApplication, it should only ever be
accessed if there is a valid QCoreApplication::instance(). Accessing this
object before QCoreApplication is created or after it's destroyed will
produce warnings and may have unpredictable behavior.
The typical use of this macro is as follows, in a global context (that is,
outside of any function bodies):
@ -61,6 +63,42 @@
this macro behaves identically to Q_GLOBAL_STATIC(). Please see that macro's
documentation for more information.
\omit
\section1 Implementation details
See \l Q_GLOBAL_STATIC implementation details for an introduction.
Q_APPLICATION_STATIC uses the same \l QGlobalStatic public class that
Q_GLOBAL_STATIC does, but instead uses a QtGlobalStatic::ApplicationHolder
template class as the template parameter. The differences to
QtGlobalStatic::Holder are:
\list
\li The ApplicationHolder class is empty. Unlike Holder, the storage is
provided as a \c {static inline} member, simply so that the static
member reset() function can access it without having to save the
pointer in a lambda.
\li The ApplicationHolder constructor is trivial; initialization of the
type is instead deferred to the \c pointer() function. This means the
C++11 thread-safe initialization of statics does not protect the
object.
\li Instead, ApplicationHolder provides a mutex (implemented as a \c
{static inline} member of type \l QBasicMutex) and locks it before
constructing or destructing the object.
\li After constructing the object, it will QObject::connect() the
QCoreApplication::destroyed() signal to a function that will in turn
destroy the object.
\li The destructor will destroy the object if the application is
exiting without first destroying the QCoreApplication object (i.e., a
call to \c ::exit) or this Q_APPLICATION_STATIC is part of a plugin
that is being unloaded.
\endlist
\endomit
\sa Q_APPLICATION_STATIC_WITH_ARGS(), QGlobalStatic
*/

View File

@ -148,7 +148,7 @@ void tst_QGlobalStatic::exception()
exceptionCaught = true;
}
QVERIFY(exceptionCaught);
QCOMPARE(Q_QGS_throwingGS::guard.loadRelaxed(), 0);
QCOMPARE(QtGlobalStatic::Holder<Q_QGS_throwingGS>::guard.loadRelaxed(), 0);
QVERIFY(!throwingGS.exists());
QVERIFY(!throwingGS.isDestroyed());
}