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/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of the QtCore module of the Qt Toolkit. ** This file is part of the QtCore module of the Qt Toolkit.
@ -44,6 +44,7 @@
#include <QtCore/qatomic.h> #include <QtCore/qatomic.h>
#include <atomic> // for bootstrapped (no thread) builds
#include <type_traits> #include <type_traits>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@ -55,92 +56,92 @@ enum GuardValues {
Uninitialized = 0, Uninitialized = 0,
Initializing = 1 Initializing = 1
}; };
}
#define Q_GLOBAL_STATIC_INTERNAL_HOLDER(ARGS) \ template <typename QGS> struct Holder
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); \
} \
};
#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
#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; \
}
// this class must be POD, unless the compiler supports thread-safe statics
template <typename T, T *(&innerFunction)(), QBasicAtomicInt &guard>
struct QGlobalStatic
{ {
typedef T Type; using Type = typename QGS::QGS_Type;
using PlainType = std::remove_cv_t<Type>;
bool isDestroyed() const { return guard.loadRelaxed() <= QtGlobalStatic::Destroyed; } static constexpr bool ConstructionIsNoexcept = noexcept(QGS::innerFunction(nullptr));
bool exists() const { return guard.loadRelaxed() == QtGlobalStatic::Initialized; } std::aligned_union_t<1, PlainType> storage;
static inline QBasicAtomicInteger<qint8> guard = { QtGlobalStatic::Uninitialized };
Holder() noexcept(ConstructionIsNoexcept)
{
QGS::innerFunction(pointer());
guard.storeRelaxed(QtGlobalStatic::Initialized);
}
~Holder()
{
guard.storeRelaxed(QtGlobalStatic::Destroyed);
std::atomic_thread_fence(std::memory_order_acquire); // avoid mixing stores to guard and *pointer()
pointer()->~PlainType();
}
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 *() operator Type *()
{ {
if (isDestroyed()) if (isDestroyed())
return nullptr; return nullptr;
return innerFunction(); return instance();
} }
Type *operator()() Type *operator()()
{ {
if (isDestroyed()) if (isDestroyed())
return nullptr; return nullptr;
return innerFunction(); return instance();
} }
Type *operator->() Type *operator->()
{ {
Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC", Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC",
"The global static was used after being destroyed"); "The global static was used after being destroyed");
return innerFunction(); return instance();
} }
Type &operator*() Type &operator*()
{ {
Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC", Q_ASSERT_X(!isDestroyed(), "Q_GLOBAL_STATIC",
"The global static was used after being destroyed"); "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) \ #define Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \
QT_WARNING_PUSH \ namespace { struct Q_QGS_ ## NAME { \
QT_WARNING_DISABLE_CLANG("-Wunevaluated-expression") \ typedef TYPE QGS_Type; \
namespace { namespace Q_QGS_ ## NAME { \ static void innerFunction(void *pointer) \
typedef TYPE Type; \ noexcept(noexcept(std::remove_cv_t<QGS_Type> ARGS)) \
QBasicAtomicInt guard = Q_BASIC_ATOMIC_INITIALIZER(QtGlobalStatic::Uninitialized); \ { \
Q_GLOBAL_STATIC_INTERNAL(ARGS) \ new (pointer) QGS_Type ARGS; \
} } \ } \
static QGlobalStatic<TYPE, \ }; } \
Q_QGS_ ## NAME::innerFunction, \ static QGlobalStatic<QtGlobalStatic::Holder<Q_QGS_ ## NAME>> NAME; \
Q_QGS_ ## NAME::guard> NAME; \ /**/
QT_WARNING_POP
#define Q_GLOBAL_STATIC(TYPE, NAME) \ #define Q_GLOBAL_STATIC(TYPE, NAME) \
Q_GLOBAL_STATIC_WITH_ARGS(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/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of the documentation of the Qt Toolkit. ** This file is part of the documentation of the Qt Toolkit.
@ -195,11 +195,11 @@
\omit \omit
\section1 Compatibility with Qt 4 and Qt 5.0 \section1 Compatibility with Qt 4 and Qt 5.0
This macro, in its current form and behavior, was introduced in Qt 5.1. This macro, in its current behavior, was introduced in Qt 5.1. Prior to
Prior to that version, Qt had another macro with the same name that was that version, Qt had another macro with the same name that was private API.
private API. This section is not meant to document how to use This section is not meant to document how to use Q_GLOBAL_STATIC in those
Q_GLOBAL_STATIC in those versions, but instead to serve as a porting guide versions, but instead to serve as a porting guide for Qt code that used
for Qt code that used those macros. those macros.
The Qt 4 Q_GLOBAL_STATIC macro differed in behavior in the following ways: The Qt 4 Q_GLOBAL_STATIC macro differed in behavior in the following ways:
@ -218,24 +218,38 @@
\section1 Implementation Details \section1 Implementation Details
Q_GLOBAL_STATIC is implemented by creating a QBasicAtomicInt called the \c Q_GLOBAL_STATIC is implemented by creating a type called \c Q_QGS_NAME
guard and a free, inline function called \c innerFunction. The guard where \c NAME is the name of the variable the user passed to the macro,
variable is initialized to value 0 (chosen so that the guard can be placed inside an unnamed namespace, and a \c static variable of \l QGlobalStatic
in the .bss section of the binary file), which denotes that construction type, named \c NAME. The use of unnamed namespaces forces the compiler to emit
has not yet taken place (uninitialized). The inner function is implemented non-exported symbols, often local to the translation unit, and this
by the helper macro Q_GLOBAL_STATIC_INTERNAL. 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 The "QGS" type is a \c struct containing a \c typedef to \a TYPE and a
parameters to QGlobalStatic, along with the type \a Type. Both should also static member function that receives a pointer to storage suitable to hold
have static linkage or be placed inside an anonymous namespace, so that the an instance of \a TYPE. It will initialize the storage using a placement \c
visibility of Q_GLOBAL_STATIC is that of a global static. To permit new and the variadic arguments.
multiple Q_GLOBAL_STATIC per translation unit, the guard variable and the
inner function must have unique names, which can be accomplished by The majority of the work is done by the public \l QGlobalStatic class and
concatenating with \a VariableName or by placing them in a namespace that the private \c QtGlobalStatic::Holder class. The \c Holder class has a
has \a VariableName as part of the name. To simplify and improve non-static, trivial member of suitable size and alignment to hold \a TYPE
readability on Q_GLOBAL_STATIC_INTERNAL, we chose the namespace solution. (a \c{std::aligned_union_t} or a \c{std::aligned_storage_t}). The
It's also required for C++98 builds, since static symbols cannot be used as constructor calls the "QGS" type's static member function with a pointer to
template parameters. 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: 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 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. 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 \endomit
\sa Q_GLOBAL_STATIC_WITH_ARGS(), QGlobalStatic \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 This function returns \c true if the global static object has already
completed destruction (that is, if the destructor for the type 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 This function returns \c true if the global static object has already
completed initialization (that is, if the constructor for the type has completed initialization (that is, if the constructor for the type has
@ -436,7 +422,7 @@
/*! /*!
\keyword qglobalstatic-operator-type-ptr \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 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 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 \deprecated
This function returns the address of the contents of this global static. If 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 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 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 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 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 The Qt Company Ltd.
** Copyright (C) 2021 Intel Corporation.
** Contact: https://www.qt.io/licensing/ ** Contact: https://www.qt.io/licensing/
** **
** This file is part of the QtCore module of the Qt Toolkit. ** This file is part of the QtCore module of the Qt Toolkit.
@ -41,58 +42,75 @@
#define QAPPLICATIONSTATIC_H #define QAPPLICATIONSTATIC_H
#include <QtCore/QMutex> #include <QtCore/QMutex>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qglobalstatic.h> #include <QtCore/qglobalstatic.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
#define Q_APPLICATION_STATIC_INTERNAL_HOLDER(ARGS) \ namespace QtGlobalStatic {
Q_GLOBAL_STATIC_INTERNAL_HOLDER(ARGS) \ template <typename QAS> struct ApplicationHolder
struct LifecycleHolder : public Holder \ {
{ \ using Type = typename QAS::QAS_Type;
LifecycleHolder() { connectLifecycle(); } \ using PlainType = std::remove_cv_t<Type>;
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); \
}); \
} \
};
#define Q_APPLICATION_STATIC_INTERNAL(ARGS) \ static inline std::aligned_union_t<1, PlainType> storage;
Q_GLOBAL_STATIC_INTERNAL_DECORATION BaseType *innerFunction() \ static inline QBasicAtomicInteger<qint8> guard = { QtGlobalStatic::Uninitialized };
{ \ static inline QBasicMutex mutex {};
Q_APPLICATION_STATIC_INTERNAL_HOLDER(ARGS) \
static LifecycleHolder holder; \ static constexpr bool MutexLockIsNoexcept = noexcept(mutex.lock());
return holder.get(); \ 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) \ #define Q_APPLICATION_STATIC_WITH_ARGS(TYPE, NAME, ARGS) \
QT_WARNING_PUSH \ namespace { struct Q_QAS_ ## NAME { \
QT_WARNING_DISABLE_CLANG("-Wunevaluated-expression") \ typedef TYPE QAS_Type; \
namespace { namespace Q_QAS_ ## NAME { \ static void innerFunction(void *pointer) \
typedef TYPE BaseType; \ noexcept(noexcept(std::remove_cv_t<QAS_Type> ARGS)) \
typedef std::unique_ptr<BaseType> Type; \ { \
QBasicAtomicInt guard = Q_BASIC_ATOMIC_INITIALIZER(QtGlobalStatic::Uninitialized); \ new (pointer) QAS_Type ARGS; \
QBasicMutex theMutex; \ } \
Q_APPLICATION_STATIC_INTERNAL((new BaseType ARGS)) \ }; } \
} } \ static QGlobalStatic<QtGlobalStatic::ApplicationHolder<Q_QAS_ ## NAME>> NAME;\
static QGlobalStatic<TYPE, \ /**/
Q_QAS_ ## NAME::innerFunction, \
Q_QAS_ ## NAME::guard> NAME; \
QT_WARNING_POP
#define Q_APPLICATION_STATIC(TYPE, NAME) \ #define Q_APPLICATION_STATIC(TYPE, NAME) \
Q_APPLICATION_STATIC_WITH_ARGS(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 type will be recreated when it's accessed again once a new QCoreApplication
has been created. has been created.
Since the value is bound to the QCoreApplication it should only ever be accessed, Since the value is bound to the QCoreApplication, it should only ever be
if there is a valid QCoreApplication::instance(). 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, The typical use of this macro is as follows, in a global context (that is,
outside of any function bodies): outside of any function bodies):
@ -61,6 +63,42 @@
this macro behaves identically to Q_GLOBAL_STATIC(). Please see that macro's this macro behaves identically to Q_GLOBAL_STATIC(). Please see that macro's
documentation for more information. 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 \sa Q_APPLICATION_STATIC_WITH_ARGS(), QGlobalStatic
*/ */

View File

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