全局对象和创建顺序

时间:2013-12-17 21:17:42

标签: c++ qt qtcore

我还在学习C ++。我有一个问题。假设您的项目具有始终存在的全局对象,例如ApiManager,并且所有其他模块都可以访问它(#include)。现在我是这样做的:

部首:

class ApiManager : public QObject
{
    Q_OBJECT
public:
    explicit ApiManager(QObject *parent = 0);
signals:
public slots:
};

extern ApiManager apiMng;

来源:

ApiManager apiMng;

问题是其他对象在初始化时也需要访问,我注意到C ++全局对象是按字母顺序创建的。我想知道你是如何处理它的?为此存在一些诀窍?例如,在Free Pascal世界中,每个类模块都有initializationfinalization个部分:

Type
  TApiManager = class
end;

var ApiMng: TApiManager;

initialization
  ApiMng := TApiManager.Create;
finalization
  ApiMng.Free;

...和initialization项目模块的顺序可以在uses子句中的项目源中进行排序(如C ++中的#include)。我知道有很多方法可以做到这一点(例如使用自定义顺序初始化main.cpp中的所有内容)但是想知道C ++世界中的“好习惯”

编辑:由Q_GLOBAL_STATIC解决(在Qt 5.1中引入,但也适用于Qt 4.8),但仍有两个问题:

  1. 仍然不知道如何管理构造函数订单(以及在何处初始化它)。因为Q_GLOBAL_STATIC创建的全局对象不是在应用程序启动时创建的。它们是在首次使用时创建的。所以我需要用我的自定义订单在某处(在main.cpp?中)“触摸”这些对象。

  2. 文档说必须在body .cpp文件中调用Q_GLOBAL_STATIC,而不是在标头中调用。但是其他类没有看到这个对象。所以我创建了静态函数,它公开了对这个对象的引用:

  3. 的.cpp:

    Q_GLOBAL_STATIC(ApiManager, apiMng)
    ApiManager *ApiManager::instance()
    {
        return apiMng();
    }
    

    但是从这个主题:http://qt-project.org/forums/viewthread/13977 Q_GLOBAL_STATIC应该自动公开实例,但它不是

3 个答案:

答案 0 :(得分:7)

它们没有按字母顺序初始化,并且翻译单元之间的初始化顺序是未定义的,因为关于它的标准没有任何保证。

  

为什么全局变量是邪恶的

     

应该避免全局变量有几个原因,但主要原因是它们极大地增加了程序的复杂性。例如,假设您正在检查程序,并且您想知道名为g_nValue的变量用于什么。因为g_nValue是全局的,并且全局变量可以在整个程序中的任何地方使用,所以你必须检查每个文件的每一行!在包含数百个文件和数百万行代码的计算机程序中,您可以想象这需要多长时间!

     

其次,全局变量是危险的,因为它们的值可以被任何被调用的函数改变,并且程序员没有简单的方法知道这会发生。

     

为什么在不必要时应避免使用全局变量

     

非本地化 - 当各个元素的范围有限时,源代码最容易理解。全局变量可以被程序的任何部分读取或修改,使得难以记住或推理每种可能的用途。

     

无访问控制或约束检查 - 程序的任何部分都可以获取或设置全局变量,并且可以轻松地删除或忘记有关其使用的任何规则。 (换句话说,获取/设置访问器通常比直接数据访问更受欢迎,对于全局数据更是如此。)通过扩展,缺少访问控制极大地阻碍了在您可能希望运行不受信任的代码的情况下实现安全性(例如使用第三方插件)。   隐式耦合 - 具有许多全局变量的程序通常在这些变量之间存在紧密耦合,以及变量和函数之间的耦合。将耦合项目分组为有凝聚力的单元通常会带来更好的计划。

     

并发问题 - 如果多个执行线程可以访问全局变量,则需要进行同步(并且经常被忽略)。当动态链接模块与全局变量时,即使在几十个不同的上下文中测试的两个独立模块是安全的,组合系统也可能不是线程安全的。

     

命名空间污染 - 全球名称随处可见。当您认为使用本地(通过拼写错误或忘记声明本地)时,您可能会在不知不觉中最终使用全局,反之亦然。此外,如果您必须将具有相同全局变量名称的模块链接在一起,如果幸运的话,您将收到链接错误。如果您运气不好,链接器将只处理与同一对象同名的所有用途。   内存分配问题 - 某些环境具有内存分配方案,使得全局变量分配变得棘手。在构建者和#34;的语言中尤其如此。除了分配之外还有副作用(因为,在这种情况下,你可以表达两个全局相互依赖的不安全情况)。此外,在动态链接模块时,可能不清楚不同的库是否有自己的全局变量实例或者是否共享全局变量。

     

测试和限制 - 使用全局变量的来源有点难以测试,因为人们不能轻易地设置“清洁”。运行之间的环境。更一般地,利用未明确提供给该源的任何类型的全局服务(例如,读取和写入文件或数据库)的源很难以相同的原因进行测试。对于通信系统,测试系统不变量的能力可能需要运行多个副本。系统同时受到任何使用共享服务(包括全局内存)的严重阻碍,这些服务并未作为测试的一部分提供共享。

一般情况下,请避免使用全局变量作为经验法则。如果您确实需要,请使用Q_GLOBAL_STATIC

  

创建一个名为VariableName的QGlobalStatic类型的全局和静态对象,其行为是指向Type的指针。 Q_GLOBAL_STATIC创建的对象在第一次使用时初始化,这意味着它不会增加应用程序或库的加载时间。此外,该对象在所有平台上以线程安全的方式初始化。

您也可以使用Q_GLOBAL_STATIC_WITH_ARGS。在这里,您可以从文档中找到一些内联突出显示:

  

创建一个名为VariableName的QGlobalStatic类型的全局和静态对象,由参数Arguments初始化,并作为指向Type的指针。 Q_GLOBAL_STATIC_WITH_ARGS创建的对象在第一次使用时初始化,这意味着它不会增加应用程序或库的加载时间。此外,该对象在所有平台上以线程安全的方式初始化。

有些人也倾向于创建一个包装它们的函数,但它们并没有显着降低复杂性,并且它们最终要么忘记使这些函数成为线程安全的,要么就会增加复杂性。忘记这样做了。什么时候可以。

答案 1 :(得分:6)

全局对象的初始化顺序仅在翻译单元中定义(从上到下)。翻译单位之间无法保证。典型的解决方法是将对象包装到函数中并返回对本地对象的引用:

ApiManager& apiMng() {
    static ApiManager rc;
    return rc;
}

本地对象在第一次调用函数时初始化(并且,当以线程安全的方式使用C ++ 11时)。这样,可以以有用的方式对全局访问对象的构造顺序进行排序。

那就是说,不要使用全局对象。他们造成的伤害大于好处。

答案 2 :(得分:0)

C ++世界的好习惯是不惜一切代价避免全局对象 - 对象越局部化就越好。

如果您必须拥有全局对象,我认为最好的方法是在main中按自定义顺序初始化对象 - 明确初始化顺序。你正在使用qt的事实是在main中初始化的另一个论点 - 你可能想要在任何其他QObject之前初始化QApplication(它需要argc和argv作为输入参数)。

相关问题