全局对象和创建顺序

Dib*_*ibo 5 c++ qt qtcore

我还在学习C++.我有一个问题.假设您的项目具有始终存在的全局对象,例如ApiManager,所有其他模块都可以访问它(by #include).现在我通过以下方式做到:

标题:

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

extern ApiManager apiMng;
Run Code Online (Sandbox Code Playgroud)

资源:

ApiManager apiMng;
Run Code Online (Sandbox Code Playgroud)

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

Type
  TApiManager = class
end;

var ApiMng: TApiManager;

initialization
  ApiMng := TApiManager.Create;
finalization
  ApiMng.Free;
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

的.cpp:

Q_GLOBAL_STATIC(ApiManager, apiMng)
ApiManager *ApiManager::instance()
{
    return apiMng();
}
Run Code Online (Sandbox Code Playgroud)

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

lpa*_*app 7

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

为什么全局变量是邪恶的

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

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

为什么在不必要的时候应该避免全局变量

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

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

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

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

测试和限制 - 使用全局变量的源更难以测试,因为在运行之间无法轻易建立"干净"的环境.更一般地,利用未明确提供给该源的任何类型的全局服务(例如,读取和写入文件或数据库)的源很难以相同的原因进行测试.对于通信系统,测试系统不变量的能力可能需要同时运行系统的多个"副本",这在任何使用共享服务(包括全局内存)时都会受到很大阻碍,这些共享服务不作为测试的一部分提供共享.

一般来说,请避免使用全局变量作为经验法则.如果您确实需要它们,请使用Q_GLOBAL_STATIC.

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

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

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

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


Die*_*ühl 6

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

ApiManager& apiMng() {
    static ApiManager rc;
    return rc;
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • @LaszloPapp:你可能更喜欢Qt但是1.它不是C++标准的一部分,而且2.C++标准保证了我的方法的线程安全性(见6.7 [stmt.dcl]第4段说服自己;有关声明是在本段末尾). (2认同)
  • @LaszloPapp:请注意,我没有发布到Qt"线程"!我已经回答了一个标记为C++并给出C++答案的问题.我不会避免回答C++问题,因为它可能会贴上一些我可能不知道的技术.您可以自由地发表评论,表明它不是特定上下文中的推荐方法,但这不是您所做的.相反,你选择陈述"那是错误的恕我直言"继续宣传所有C++用户无法访问的东西.我是否应该要求您停止评论C++问题? (2认同)