用于C ++单例类实例的堆/动态与静态内存分配 [英] Heap/dynamic vs. static memory allocation for C++ singleton class instance

查看:172
本文介绍了用于C ++单例类实例的堆/动态与静态内存分配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的具体问题是,在C ++中实施单例类时,以下两个代码之间是否存在实质性差异关于性能,侧面问题或某事:

  class singleton 
{
// ...
static singleton& getInstance()
{
//在堆上分配
static singleton * pInstance = new singleton();
return * pInstance;
}
// ...
};

并且:

  class singleton 
{
// ...
static singleton& getInstance()
{
//使用静态变量
static singleton instance;
return instance;
}
// ...
};



(请注意,基于堆的实施中的解引用不应影响性能,因为AFAIK没有为解引用生成额外的机器代码。)似乎只是语法上的区别于指针的问题。)



UPDATE: strong>



我有很多有趣的答案和意见,我在这里总结一下。 (




  • 在使用 static 局部变量的单例中,类析构函数在进程终止时自动调用,而在动态分配情况下,你必须在某个时候管理对象破坏,例如通过使用智能指针:



  static singleton& getInstance(){
static std :: auto_ptr< singleton> instance(new singleton());
return * instance.get();
}




  • 使用动态分配的单例比静态单例变量,如在后面的情况下,单例对象的所需内存是(总是?)在进程启动时保留(作为加载所需的整个内存的一部分)程序),并且只有单例构造函数的调用被推迟到 getInstance()调用时间。当 sizeof(singleton)很大时,这可能很重要。


  • +11。


  • 动态分配情况使用一级间接访问单例对象,而在静态单例





在对象情况下,确定对象的直接地址并在编译时进行硬编码
PS:根据@ TonyD的回答,我已更正了原始帖子中使用的术语。

方案


  • new 版本显然需要在运行时分配内存,在编译时分配内存(但两者都需要做相同的构造)


  • new 版本将不会在程序终止时调用对象的析构函数,但非< - code>新版本将:您可以使用智能指针来更正此




    • 您需要小心,一些静态/命名空间范围对象的析构函数不调用您的单例在其静态本地实例的析构函数运行...如果你关注关于这一点,你应该更多地了解Singleton生命周期和管理它们的方法。在C ++ 03下,Andrei Alexandrescu的现代C ++设计具有非常可读的处理方式。


  • 线程安全。


  • 在C ++ 11下,它是安全的:6.7(我相信GCC倾向于,而Visual Studio不倾向于确认/ .4如果控制在变量初始化时同时进入声明,并发执行将等待初始化完成。 (无递归)。




讨论重编译时与运行时分配&初始化



根据您对摘要和几条评论的措辞,我怀疑您并未完全理解分配和初始化的一个微妙方面的静态变量....



假设你的程序有3个局部静态32位 int s - a b c 编译一个二进制,告诉OS加载器为这些静态留下3x32位= 12字节的内存。编译器确定这些变量中的每个变量的偏移量:它可以在数据段中的偏移量1000 hex处放置 a b 在1004处,以及 c 在1008处。当程序执行时,OS加载程序不需要为每个单独分配存储器 - 12字节,它可以或可以不被具体地请求0-初始化,但是它可能想要做任何确保该过程不能看到剩余的来自其他用户的程序的存储器内容。程序中的机器代码指令通常将对偏移1000,1004,1008进行硬编码以访问 a b c - 因此在运行时不需要分配这些地址。



动态内存分配不同因为指针(例如 p_a p_b p_c )将如上所述在编译时被赋予地址,但另外:





这种动态的方法显然更为复杂。


My specific question is that when implementing a singleton class in C++, is there any substantial differences between the two below codes regarding performance, side issues or something:

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

and this:

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


(Note that dereferencing in the heap-based implementation should not affect performance, as AFAIK there is no extra machine-code generated for dereferencing. It's seems only a matter of syntax to distinguish from pointers.)

UPDATE:

I've got interesting answers and comments which I try to summarize them here. (Reading detailed answers is recommended for those interested.)‎:

  • In the singleton using static local variable, the class destructor is automatically invoked at process termination, whereas in the dynamic allocation case, you have to manage object destruction someway at sometime, e.g. by using smart pointers:

    static singleton& getInstance() {
        static std::auto_ptr<singleton> instance (new singleton());
        return *instance.get(); 
    }

  • The singleton using dynamic allocation is "lazier" than the static singleton variable, as in the later case, the required memory for the singleton object is (always?) reserved at process start-up (as part of the whole memory required for loading program) and only calling of the singleton constructor is deferred to getInstance() call-time. This may matter when sizeof(singleton) is large.

  • Both are thread-safe in C++11. But with earlier versions of C++, it's implementation-specific.

  • The dynamic allocation case uses one level of indirection to access the singleton object, whereas in the static singleton object case, direct address of the object is determined and hard-coded at compile-time.


P.S.: I have corrected the terminology I'd used in the original posting according to the @TonyD's answer.

解决方案

  • the new version obviously needs to allocate memory at run-time, whereas the non-pointer version has the memory allocated at compile time (but both need to do the same construction)

  • the new version won't invoke the object's destructor at program termination, but the non-new version will: you could use a smart pointer to correct this

    • you need to be careful that some static/namespace-scope object's destructors don't invoke your singleton after its static local instance's destructor has run... if you're concerned about this, you should perhaps read a bit more about Singleton lifetimes and approaches to managing them. Andrei Alexandrescu's Modern C++ Design has a very readable treatment.
  • under C++03, it's implementation-defined whether either will be thread safe. (I believe GCC tends to be, whilst Visual Studio tends not -comments to confirm/correct appreciated.)

  • under C++11, it's safe: 6.7.4 "If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization." (sans recursion).

Discussion re compile-time versus run-time allocation & initialisation

From the way you've worded your summary and a few comments, I suspect you're not completely understanding a subtle aspect of the allocation and initialisation of static variables....

Say your program has 3 local static 32-bit ints - a, b and c - in different functions: the compiler's likely to compile a binary that tells the OS loader to leave 3x32-bits = 12 bytes of memory for those statics. The compiler decides what offsets each of those variables is at: it may put a at offset 1000 hex in the data segment, b at 1004, and c at 1008. When the program executes, the OS loader doesn't need to allocate memory for each separately - all it knows about is the total of 12 bytes, which it may or may not have been asked specifically to 0-initialise, but it may want to do anyway to ensure the process can't see left over memory content from other users' programs. The machine code instructions in the program will typically hard-code the offsets 1000, 1004, 1008 for accesses to a, b and c - so no allocation of those addresses is needed at run-time.

Dynamic memory allocation is different in that the pointers (say p_a, p_b, p_c) will be given addresses at compile time as just described, but additionally:

  • the pointed-to memory (each of a, b and c) has to be found at run-time (typically when the static function first executes but the compiler's allowed to do it earlier as per my comment on the other answer), and
    • if there's too little memory currently given to the process by the Operating System for the dynamic allocation to succeed, then the program library will ask the OS for more memory (e.g. using sbreak()) - which the OS will typically wipe out for security reasons
    • the dynamic addresses allocated for each of a, b and c have to be copied back into the pointers p_a, p_b and p_c.

This dynamic approach is clearly more convoluted.

这篇关于用于C ++单例类实例的堆/动态与静态内存分配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆