QGlobalStatic 结构体
template <typename Holder> struct QGlobalStaticQGlobalStatic 类用于实现全局静态对象。更多...
头文件 | #include <QGlobalStatic> |
CMake | find_package(Qt6 REQUIRED COMPONENTS Core) target_link_libraries(mytarget PRIVATE Qt6::Core) |
qmake | QT += core |
注意: 此结构体中所有函数都是 线程安全的。
公共类型
公共函数
bool | exists() const |
bool | isDestroyed() const |
QGlobalStatic<Holder>::Type * | operator QGlobalStatic<Holder>::Type *() |
QGlobalStatic<Holder>::Type & | operator*() |
QGlobalStatic<Holder>::Type * | operator->() |
宏
(自 6.3 版起) | Q_APPLICATION_STATIC(类型, 变量名, ...) |
Q_GLOBAL_STATIC(类型, 变量名, ...) |
详细描述
当使用 Q_GLOBAL_STATIC() 时,QGlobalStatic 类是导出的前端 API。有关何时使用此宏及其要求的讨论,请参阅宏的文档。
通常,您永远不会直接使用此类,而是使用 Q_GLOBAL_STATIC() 或 Q_GLOBAL_STATIC_WITH_ARGS() 宏,如下所示
Q_GLOBAL_STATIC(MyType, staticType)
上面的示例创建了一个名为 staticType
的 QGlobalStatic 类型的对象。在上面的声明之后,可以使用 staticType
对象,就像它是一个指针一样,保证恰好初始化一次。除了作为指针使用外,该对象还提供两种方法来确定全局对象的当前状态:exists() 和 isDestroyed()。
另请参阅:Q_GLOBAL_STATIC() 和 Q_GLOBAL_STATIC_WITH_ARGS()。
成员类型文档
[别称]
QGlobalStatic::Type
此类与传递给 Q_GLOBAL_STATIC() 或 Q_GLOBAL_STATIC_WITH_ARGS() 宏的 Type
参数等效。它用于一些函数的返回类型中。
成员函数文档
[noexcept]
bool QGlobalStatic::exists() const
此函数返回true
,如果全局静态对象已初始化完成(即,如果该类型的构造函数已返回)。具体来说,请注意,如果初始化仍在进行中,则此函数将返回false
。
一旦此函数返回了一次true,除非程序重新启动或包含全局静态对象的插件或库被卸载和重新加载,否则它将不再返回false。
可以在程序执行的任何时刻安全地调用此函数:它不会失败,也不能造成死锁。此外,如果它们尚未创建,它不会创建它们。
如果可以确定全局静态对象的初始条件并且希望避免一个可能昂贵的构造操作,此函数很有用。
例如,在下面的代码示例中,此函数用于断开全局静态对象globalState
的创建,并返回一个默认值
Q_GLOBAL_STATIC(MyType, globalState) QString someState() { if (globalState.exists()) return globalState->someState; return QString(); }
线程安全注意事项:此函数在任意时间从任意线程中调用都是线程安全的,并且总是会返回有效的回复。但由于构造的非原子性质,构造完成后,此函数可能会在一小段时间内返回false。
内存排序注意事项:此函数不提供任何内存排序保证。这由返回指针或引用到内容的访问器函数提供。如果您绕过访问器函数并尝试访问由构造函数设置的一些全局状态,请确保使用由QAtomicInt或QAtomicPointer提供的正确的内存排序语义。
另请参阅isDestroyed()。
[noexcept]
bool QGlobalStatic::isDestroyed() const
此函数返回true
,如果全局静态对象已初始化完成(即,如果该类型的析构函数已返回)。具体来说,请注意,如果析构仍在进行中,则此函数将返回false
。
一旦此函数返回了一次true,除非程序重新启动或包含全局静态对象的插件或库被卸载和重新加载,否则它将不再返回false。
可以在程序执行的任何时刻安全地调用此函数:它不会失败,也不能造成死锁。此外,如果它们尚未创建,它不会创建它们。
此函数在程序关闭期间执行的代码中很有用,可以确定是否还可以访问内容。
另请参阅exists()。
QGlobalStatic<Holder>::Type *QGlobalStatic::operator QGlobalStatic<Holder>::Type *()
此函数返回全局静态内容的地址。如果内容尚未创建,此函数将线程安全地创建它们。如果内容已被销毁,此函数将返回一个空指针。
此函数可以用于将全局静态内容的指针存储在局部变量中,从而避免多次调用该函数。例如,Q_GLOBAL_STATIC的实现在效率上已经相当高,但在性能关键部分可能会有所帮助。
Q_GLOBAL_STATIC(MyType, globalState) QString someState() { MyType *state = globalState; if (!state) { // we're in a post-destruction state return QString(); } if (state->condition) return state->value1; else return state->value2; }
另请参阅operator->()和operator*()。
QGlobalStatic<Holder>::Type &QGlobalStatic::operator*()
此函数返回对全局静态内容的引用。如果内容尚未创建,此函数将线程安全地创建它们。
这个函数不会检查内容是否已经被销毁。如果在对象已被销毁后调用此函数,它将返回一个无效的引用,该引用不得使用。
QGlobalStatic<Holder>::Type *QGlobalStatic::operator->()
此函数返回此全局静态内容的地址。如果内容尚未创建,此函数将安全地创建它。
此函数不检查内容是否已被销毁,且永远不会返回null。如果在对象被销毁后调用此函数,它将返回一个悬空指针,该指针不应被解引用。
宏文档
[自6.3起]
Q_APPLICATION_STATIC(Type, VariableName, ...)
此宏扩展了 Q_GLOBAL_STATIC 并创建一个全局和静态对象,类型为 QGlobalStatic,名称为 VariableName,由可变参数初始化,并以Type的指针方式行为,其中该类型的实际生命周期绑定到QCoreApplication。由 Q_APPLICATION_STATIC 创建的对象在首次使用时初始化自己,这意味着它不会增加应用程序或库的加载时间。此外,对象在所有平台上都以线程安全的方式初始化。
与仅应在程序退出时销毁类型的Q_GLOBAL_STATIC相反,这里的实际生命周期绑定到QCoreApplication的生存期。这使得它非常适合存储半静态的QObjects,这些对象也应该在QCoreApplication销毁时销毁。这意味着类型将在QCoreApplication发出销毁信号时被删除。如果再次访问对象时创建了新的QCoreApplication,则允许对象被重新创建。
由于值绑定到QCoreApplication,因此只有当存在有效的QCoreApplication::instance()时才应访问它。在创建QCoreApplication之前或销毁后访问此对象将产生警告,并且可能具有不可预测的行为。
此宏的典型用法如下,在全局上下文(即任何函数体之外)
Q_APPLICATION_STATIC(MyQObjectType, staticType, "some string", function())
请注意,此宏在每次对象构造时都会评估传递给可变数量的参数的参数,因此在上面的示例中,如果对象被重新创建,函数function
将被调用多次。
除了值也绑定到QCoreApplication的生命周期外,此宏的行为与Q_GLOBAL_STATIC相同。请参阅该宏的文档以获取更多信息。
线程保证
Q_APPLICATION_STATIC 宏确保对象只初始化一次(每个 QCoreApplication 的生命周期),即使多个线程尝试同时访问这个对象。这是通过为每个对象提供一个互斥锁来实现的;应用程序和库开发者需要注意,他们的对象将以互斥锁锁定的方式进行构造,因此不能重新进入同一对象的初始化过程,否则将发生死锁。
对象的销毁没有线程安全机制:当 QCoreApplication 的析构函数开始运行时,用户代码不能访问此对象。用户代码必须确保不会发生这种情况,例如在主线程的事件循环退出后不再访问它。
与 Q_GLOBAL_STATIC 类似,Q_APPLICATION_STATIC 在对象创建完成后不提供对对象访问的线程安全保证。这需要用户代码确保不会发生竞态数据访问。
如果在这次操作创建的对象是 QObject,其关联的线程将是在创建它的线程。它将在主线程中销毁,因此建议在销毁之前将其移动到主线程或没有任何线程。如果在有问题的类的构造函数中这样做是一个合理的解决方案,如果不能保证主线程将是初始化对象的那一个。
此宏是 Qt 6.3 引入的。
另请参阅Q_GLOBAL_STATIC 和 QGlobalStatic。
Q_GLOBAL_STATIC(Type, VariableName, ...)
创建一个类型为 QGlobalStatic 的全局和静态对象,名为 VariableName,行为类似于指向 Type 的指针。由 Q_GLOBAL_STATIC 创建的对象在首次使用时初始化自己,这意味着它不会增加应用程序或库的加载时间。此外,对象在所有平台上以线程安全的方式初始化。
自 Qt 6.3 以来,此宏允许使用可变参数,这些参数用于初始化对象,因此不再需要 Q_GLOBAL_STATIC_WITH_ARGS。请注意,与旧宏不同,参数不需要额外的括号。
此宏的典型用法如下,在全局上下文(即任何函数体之外)
Q_GLOBAL_STATIC(MyType, staticType)
此宏旨在替换不 POO (Plain Old Data,或 C++11 术语中的非平凡类型) 的全局静态对象,故得名。例如,以下 C++ 代码创建一个全局静态对象
static MyType staticType;
与 Q_GLOBAL_STATIC 相比,假设 MyType
是一个具有构造函数、析构函数或其他非 POO 的类或结构,上述代码有以下缺点
- 它需要在加载时初始化
MyType
(即,当库或应用程序加载时调用MyType
的默认构造函数); - 即使它从未使用,也会初始化类型;
- 不同翻译单位之间初始化和析构的顺序无法确定,可能导致在使用前初始化或在使用后析构;
Q_GLOBAL_STATIC 宏通过保证首次使用时的线程安全初始化并允许用户查询类型是否已被销毁,从而解决了上述所有问题,以避免使用已销毁对象的(问题)(请参阅 QGlobalStatic::isDestroyed())。
构造函数和析构函数
对于 Q_GLOBAL_STATIC,类型 Type
必须能够公开进行默认构造和公开析构。对于 Q_GLOBAL_STATIC_WITH_ARGS(),必须有一个与传递的参数匹配的公开构造函数。
无法使用Q_GLOBAL_STATIC与具有受保护或私有默认构造函数或析构函数的类型(对于Q_GLOBAL_STATIC_WITH_ARGS(),则需要匹配参数的受保护或私有构造函数)。如果相关类型将这些成员作为受保护成员,可以通过从该类型派生并创建公共构造函数和析构函数来克服这个问题。如果它们是私有的,则在派生之前需要声明一个friend。
例如,以下内容就足够基于之前定义的具有受保护默认构造函数和/或受保护析构函数(或它们是私有的,但定义了MyType为friend)的MyOtherType
来创建MyType
了。
class MyType : public MyOtherType { }; Q_GLOBAL_STATIC(MyType, staticType)
不需要为MyType
提供函数体,因为析构函数是一个隐式成员,如果没有定义其他构造函数,则默认构造函数也是一个隐式成员。但是,对于Q_GLOBAL_STATIC_WITH_ARGS(),需要一个合适的构造函数体。
class MyType : public MyOtherType { public: MyType(int i) : MyOtherType(i) {} }; Q_GLOBAL_STATIC_WITH_ARGS(MyType, staticType, (42))
另外,如果编译器支持C++11继承构造函数,可以写为:
class MyType : public MyOtherType { public: using MyOtherType::MyOtherType; }; Q_GLOBAL_STATIC_WITH_ARGS(MyType, staticType, (42))
放置
Q_GLOBAL_STATIC宏在一个必须为静态、全局作用域下的类型中创建一个对象。不能将Q_GLOBAL_STATIC宏放置在函数内(这样做会导致编译错误)。
更重要的是,这个宏应该放在源文件中,而不是头文件中。由于结果的链接对象具有静态链接,如果在头文件中使用宏并由多个源文件包含,对象将被定义多次,不会引起链接错误。相反,每个源将引用不同的对象,可能会导致隐晦且难以追踪的错误。
不建议使用的方法
请注意,此宏不建议用于POD类型或具有C++11 constexpr构造函数(简单地可构造和可析构)的类型。对于这些类型,仍建议使用常规静态,无论是全局还是局部于函数。
这个宏可以工作,但它会增加不必要的开销。
重入性、线程安全、死锁和构造时的异常安全性
Q_GLOBAL_STATIC宏创建一个对象,它会以线程安全的方式在首次使用时初始化:如果多个线程同时尝试初始化对象,则只有一个线程会继续初始化,而其他线程会等待完成。
如果初始化过程抛出异常,初始化被认定为未完成,当控制达到对象使用的地方,将再次尝试初始化。如果有线程正在等待初始化,其中一个将被唤醒以尝试初始化。
此宏不对从同一线程的重入性作出保证。如果全局静态对象在其构造函数内部直接或间接地被访问,肯定会发生死锁。
此外,如果两个Q_GLOBAL_STATIC对象在两个不同的线程上初始化,并且每个初始化序列都访问了另一个对象,则可能发生死锁。因此,建议保持全局静态构造函数简单,或者在无法做到这一点的情况下,确保在构造过程中全局静态用法之间没有交叉依赖。
析构
如果在程序的生命周期内从未使用过对象(除了QGlobalStatic::exists()和QGlobalStatic::isDestroyed()函数),则类型Type
的内容不会创建,也不会有任何退出时的操作。
如果对象已被创建,则在退出时将被销毁,类似于C语言的atexit
函数。实际上,在大多数系统上,如果在退出前从内存卸载库或插件,也会调用析构函数。
由于销毁发生在程序退出时,因此不提供线程安全保护。这包括插件或库卸载的情况。此外,由于析构函数不应该引发异常,因此也不提供异常安全性。
然而,允许递归性:在销毁期间,可以访问全局静态对象,返回的指针将与销毁开始前的相同。销毁完成后,不允许访问全局静态对象,除非在QGlobalStatic API中另有说明。
请参阅:Q_GLOBAL_STATIC_WITH_ARGS(),Q_APPLICATION_STATIC() 和 QGlobalStatic。
© 2024 Qt公司版权所有。本文档中的贡献力量为各自所有者的版权。本文档根据GNU自由文档许可证1.3版(由自由软件基金会发布)许可。Qt和相应的标志是芬兰和/或其他国家的Qt公司的注册商标。所有其他商标均为各自所有者的财产。