堆/动态与用于C ++单例类实例静态内存分配(Heap/dynamic vs. static mem

2019-07-20 11:47发布

我的具体问题是,实现当单独的类在C ++中,有关于性能方面的问题或东西两个下面的代码之间的巨大差异:

class singleton
{
    // ...
    static singleton& getInstance()
    {
        // allocating on heap
        static singleton* pInstance = new singleton();
        return *pInstance;
    }
    // ...
};

还有这个:

class singleton
{
    // ...
    static singleton& getInstance()
    {
        // using static variable
        static singleton instance;
        return instance;
    }
    // ...
};


(请注意,在基于堆的实现间接引用应该不会影响性能,因为据我所知还有解引用不会产生额外的机器代码。这似乎只是语法从指针来区分的问题。)

更新:

我有有趣的答案和意见,我在这里试着总结一下他们。 (阅读详细的解答,建议对于那些有兴趣。):

  • 在使用静态局部变量的单身,类析构函数会自动在进程终止调用,而在动态分配的情况下,你必须好歹管理对象的破坏在某个时候,例如,通过使用智能指针:
    static singleton& getInstance() {
        static std::auto_ptr<singleton> instance (new singleton());
        return *instance.get(); 
    }
  • 使用动态分配的单是“懒”比静态单变量,如在后一种情况下,对于单独的对象所需要的存储器是(总是?)在进程保留的启动(作为装载程序所需的整个存储器的一部分),只有调用单构造被推迟到getInstance()调用的时候。 当这也许无关紧要sizeof(singleton)大。

  • 无论是线程安全的C ++ 11。 但是,与早期版本的C ++的,它是实现特定的。

  • 动态分配的情况下使用一个间接层来访问单独的对象,而在静态单对象情况下,对象的直接地址被确定,并且在编译时硬编码的。


PS:我已经纠正根据@ TonyD的答案,我在原来的张贴会使用的术语。

Answer 1:

  • new版本显然需要在运行时内存分配,而非指针版本在编译时分配的内存(但都需要做同样的结构)

  • new版本将不会在程序终止时调用该对象的析构函数,但非new版本:您可以使用智能指针,以纠正这种

    • 你必须小心一些静态/命名空间范围对象的析构函数不调用它的静态本地实例的析构函数后,你的单身已经用完......如果你担心这个,你或许应该再多读一些关于辛格尔顿寿命和接近管理它们。 安德烈Alexandrescu的的现代C ++设计有一个非常可读的治疗。
  • 在C ++ 03,这是实现定义是否会要么是线程安全的。 (我相信GCC往往是,而Visual Studio中往往不会-comments确认/正确的认识。)

  • 在C ++ 11,它是安全的:6.7.4“如果控制进入的同时,而变量被初始化的声明,并发执行必须等待初始化完成。” (没有递归)。

讨论重新编译时间与运行时间分配和初始化

从你的措辞你的总结和几点意见的方式,我怀疑你没有完全理解静态变量的分配和初始化的一个细微环节....

假设你的程序有3个本地静态32位int秒- abc -在不同的功能:编译器可能编译二进制文件告诉操作系统加载器离开3x32位=对于那些静态12个字节的内存。 编译器决定什么抵消每个这些变量是:它可以把a偏移量1000十六进制的数据段, b为1004,和c在1008当程序执行时,操作系统加载器并不需要分配内存每个单独 - 所有它所知道的是总的12个字节,它可能会或可能不会被特别问到0 INITIALISE,但它可能想做反正以确保过程不能看到遗留下来的其他存储内容用户的程序。 在程序中的机器代码指令通常将硬码偏移1000,1004,1008,在访问abc -因此需要在运行时没有这些地址的分配。

动态存储器分配是,所述不同的指针(比如p_ap_bp_c )如刚刚描述的在编译时将给出地址,但额外的:

  • 所指向的内存(每abc )必须在运行时(通常当静态函数首先执行,但该编译器的允许提前按我的意见做对对方的回答)被找到,
    • 如果有目前由操作系统为动态分配成功给予处理内存太少,那么程序库将要求OS更多的内存(例如使用sbreak() -该操作系统通常会消灭出于安全考虑
    • 分配给每个的动态地址abc具有要复制回指针p_ap_bp_c

这种动态的方法显然更加令人费解。



Answer 2:

主要的区别是使用本地static关闭程序时,该对象将被销毁,而不是堆分配的对象将只是没有被销毁遗弃。

请注意,在C ++中,如果你声明函数内部静态变量将被初始化您第一次输入的范围,而不是在程序启动(就像它发生,而不是全局静态持续时间变量)。

一般来说,多年来我从因为程序的启动和关闭是微妙阶段,相当难以调试使用延迟初始化,以明确的控制初始化切换。 如果你的类没有做任何事情复杂,只是不能失败(例如,它只是一个注册表),那么即使延迟初始化是好的...否则控制别人会为你节省了不少的问题。

进入的第一指令之前崩溃的程序main或执行的最后一个指令之后main是难以调试。

用单身的懒建设的另一个问题是,如果你的代码是多线程你已经关注到有并发线程初始化,同时单身的风险。 在单个线程的上下文做初始化和关闭更加简单。



文章来源: Heap/dynamic vs. static memory allocation for C++ singleton class instance