最近,我遇到了一个C++实现的单体设计模式的实现。它看起来像这样(我从现实生活的例子中采用了它):
1 2 3 4 5 6 7 8 9 10
| // a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
}; |
根据这个声明,我可以推断实例字段是在堆上启动的。这意味着存在内存分配。对于我来说,完全不清楚的是什么时候该释放内存?还是有错误和内存泄漏?似乎在实现中存在问题。
我的主要问题是,如何以正确的方式实现它?
- 析构函数中有什么?
- 释放一些资源,但似乎从未发生过。
- 请参阅此处,了解如何定义一个被自动销毁的歌手。stackoverflow.com/questions/86582/…
- 另请参见:stackoverflow.com/questions/211237/…和stackoverflow.com/questions/270947/…以及stackoverflow.com/questions/246564/…和stackoverflow.com/questions/449436/…和stackoverflow.com/questions/335369/…
- 本文将讨论如何在C++中实现单线程以及线程安全。aristeia.com/papers/ddj%5fjul%5faug%5f2004%5frevised.pdf
- @只有西斯才是绝对的。绝大多数的问题没有单件能解决吗?当然。单身汉会自己制造麻烦吗?对。然而,我不能诚实地说它们是坏的,因为设计就是要考虑权衡和理解方法的细微差别。
- @Derekerdmann:单件是一个光荣的全局变量。无论何时全局变量适合您的设计,它们都非常适合。那又是什么时候?
- @ SBI -尝试使用JNI,在Java和C++之间进行桥接。由于JNI方法是静态的,所以与现有的C++对象层次结构接口有时需要单独调用。设计不是圣战的好地方。
- @Derekerdmann:我没有说你永远不需要一个全局变量(当你需要一个全局变量时,一个单独的变量有时更好)。我说的是它们应该尽可能少地使用。将singleton作为一种有价值的设计模式加以美化,会给人留下使用它的好印象,而不是认为它是一种黑客行为,使代码难以理解、难以维护和难以测试。这就是我发表评论的原因。到目前为止,你所说的都不反驳这一点。
- @SBI:你说的是"不要使用它们",而不是更合理的"它们应该尽可能少地使用",你后来改为——当然你看到了区别。
- @JWD:好吧,事后看来,"尽可能少"似乎很弱,我宁愿站在一边,"只要一个全局变量适合你的设计,它们就很适合你的设计。"单件是全局变量。对全局变量的建议已经是"不要使用它们!"当我在80年代开始编程时,它从未改变过。
- @SBI当您有一个类来启动一个在构建时对其加锁的设备时,单例是一个非常好的方法,可以确保只创建上述设备交互类的一个实例。单身也不错。
- @帕特:当你有一个A,它把B变成C,反过来把D变成D的时候……在这种复杂的情况下,你通常不应该做的每件事都可能是处理这种情况的小邪恶。但在这个行业工作了20年后,我得出了这样的结论:在讨论中,这种情况比实际情况更常见。(当然,你可能会因为遇到这样的情况而发表评论。但首先,我可能看到了其他更好的处理方法。而且,也花了半年时间,直到有人经过。)
- 单例设计模式用于确保应用程序从不包含给定类型的多个实例。它通常被认为是反模式deviq.com/singleton
- "请勿使用"404未找到
在2008中,我提供了一个C++ 98的单实例设计模式的实现,它是懒惰的、有保证的破坏,而不是技术上的线程安全:有谁能给我一个C++中的单样本吗?
这里是一个更新的C++ 11实现的单体设计模式,它是懒惰的、正确的破坏和线程安全的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class S
{
public:
static S& getInstance()
{
static S instance; // Guaranteed to be destroyed.
// Instantiated on first use.
return instance;
}
private:
S() {} // Constructor? (the {} brackets) are needed here.
// C++ 03
// ========
// Don't forget to declare these two. You want to make sure they
// are unacceptable otherwise you may accidentally get copies of
// your singleton appearing.
S(S const&); // Don't Implement
void operator=(S const&); // Don't implement
// C++ 11
// =======
// We can use the better technique of deleting the methods
// we don't want.
public:
S(S const&) = delete;
void operator=(S const&) = delete;
// Note: Scott Meyers mentions in his Effective Modern
// C++ book, that deleted functions should generally
// be public as it results in better error messages
// due to the compilers behavior to check accessibility
// before deleted status
}; |
请参阅这篇关于何时使用单例的文章:(不经常使用)单人间:怎么用
请参阅这两篇关于初始化顺序和如何处理的文章:静态变量初始化顺序查找C++静态初始化顺序问题
请参阅这篇描述生命周期的文章:C++函数中静态变量的生存期是多少?
请参阅本文,讨论单例线程的一些含义:singleton实例声明为getInstance方法的静态变量,它是线程安全的吗?
请参阅这篇文章,解释为什么双重检查锁定在C++上无效。C++程序员应该知道的所有常见的未定义行为是什么?多布斯博士:C++与双重检查锁定的危险:第一部分
- @Fnieto:谢谢。我试图用构造函数s()表示的内容应该声明为private。因为singleton将有其他需要初始化的成员(否则为什么有singleton)(注意,没有不实现),所以当您声明一个构造函数时,它将需要是私有的。
- 好答案。但需要注意的是,这不是线程安全的stackoverflow.com/questions/1661529/…
- 上面已经提到:stackoverflow.com/questions/449436/…
- @ Varuna:在C++ 11中,这是线程安全的。
- §;6.7[stmt.dcl]p4 If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.。
- @佐尔特尼:很多人不知道你刚才做了什么:)
- 这个方法的问题在于,当实例被销毁时,它几乎是不确定的,因此您可能很容易得到一个悬空的引用。我不推荐这种方法。
- @马克西米耶戈鲁什金:当这一点被破坏时,是非常明确的(没有歧义)。请参阅:stackoverflow.com/questions/246564/…
- @Lokiastari在技术上是由标准定义的。但是,getInstance()返回对寿命有限的对象的引用的事实可能会使调用者错误地感觉到引用始终有效的安全性。最让我恼火的是getInstance()中隐藏布尔值的运行时检查,它负责保护实例的构造。这就是为什么我更喜欢一个普通的全局对象而不是这个模式,可能是使用Schwarz计数器来强制执行所需的动态初始化/破坏顺序,就像对std::cout和friends一样,也就是说,在访问上没有开销。
- @洛基亚斯塔里…使用简单的访问语法std::cout,而不是主观上丑陋的std::ostream::get_cout()。
- What irks me most though is the run-time check of the hidden boolean in getInstance()是对实现技术的一种假设。不需要假设它还活着。请参阅stackoverflow.com/a/335746/14065,您可以强制执行某个情况,使其始终处于活动状态(开销比Schwarz counter小)。全局变量在初始化顺序(跨编译单元)上有更多的问题,因为您不强制执行顺序。该模型的优点是1)初始化迟缓。2)执行命令的能力(施瓦茨有帮助,但更丑)。是的,get_instance()更丑。
- 下面访问实例的方法有什么问题吗?s*s=s->getInstance();//我会修改getInstance(),这样就不会出现编译错误,我可以在这里获取实例。但我的问题是,这个用于访问实例的实现是正确的吗?
- @斯雷不这样做。返回指针是一个大问题。使用上面的设计。
- 好的,谢谢你,洛基阿斯塔里,将只使用上述设计。但我对"返回指针是个大问题"这句话不太清楚。请解释一下。
- @斯雷:你要返回的指针是谁?这就引出了谁应该删除它的问题。这也增加了我应该何时删除它的问题(我确定我是最后一个用户)。在现代C++中,看到指针被传递是不常见的,因为它们没有所有权语义的含义。通常,如果您保留所有权(如上所述),或者对于动态分配的对象,您会返回一个适当的智能指针来指示所需的所有权语义类型(查找std::shared_ptr)。
- 为什么在私有默认构造函数中需要?如果您想在初始化单例的过程中加载一些数据,则不需要也不需要。
- @Santoshtiwari:在最初的版本中,我把body从构造器中去掉了(因为我不认为这是必要的)。编辑器添加了大括号和注释,使代码可以编译,如图所示。
- @科尔:别再乱弄答案了,你没有添加任何有用的东西。赋值运算符的返回类型不重要。
- @我很抱歉。我认为,如果为赋值运算符使用正确/常见的返回类型,代码的可读性会提高一点。
- @KOL:correct return type。你在混淆术语。您所指的是编译器提供的默认生成的赋值运算符。这不是正确的。正确的分配运算符取决于情况。在这种情况下,使用void更为正确。注意:任何赋值操作符都会阻止编译器生成默认的赋值操作符。
- @Lokiastari好吧,"平常"是更好的词,参见en.wikipedia.org/wiki/…
- @科尔:不,不是平常的事。仅仅因为初学者不经思考就复制和粘贴代码,并不能使它成为通常的代码。您应该始终查看用例,并确保赋值操作符执行预期的操作。复制和粘贴代码会导致错误。
- 我认为这个实现是C++中的反模式。您可以使用全局对象和schwarz计数器在首次使用之前初始化对象(如std::cout和friends已初始化几十年),从而达到所需的效果。
- @Maximyegorushkin:当然可以使用全局对象和Schwarz counter来表示全局可变状态。但它有不同的属性(适用于std::cout和family)。比如没有延迟初始化。它是一种不同的技术,用于不同的问题。这里的反模式不是这个实现,而是全局可变状态的使用。
- @lokiastari语法大小,它是instance.foo()对get_instance().foo()。您能详细介绍一下您所指的不同属性吗,因为在我看来,初始化方面,它与使用Schwarz计数器的全局对象没有什么不同?
- @maximyegorushkin:schwarts计数器总是初始化对象。以上仅在首次使用时初始化。
- 如果您不想实现一个函数,可以像S(S const&) = delete;那样声明它,而不是在私有修饰符下声明。使其更具可读性。
- 如果我想要对单例进行自定义初始化怎么办?
- @Brita_uuu:这只是一个如何让它与静态成员一起工作的例子。事实上,这不是你想要的。您真的应该将单例模式与创建者模式结合起来(比如工厂,但任何创建者模式都可以)。因为您想从检索逻辑中取消创建逻辑的耦合。实际上,上面的行看起来更像:static Singelton& instance = SingetonFactory::create();在工厂中,您可以拥有适当的功能并注册希望如何创建对象。
- 删除的复制构造函数和复制分配运算符声明为private删除成员函数的原因是什么?Scott Meyers在其有效的现代C++书中提到,删除函数通常应该是公开的,因为它会导致更好的错误消息,因为编译器BaaviVo检查删除状态之前的可访问性。
- @施尼格斯:完成。
- S构造函数(因此getInstance方法)是否可以有一些参数?
- @史蒂芬斯坦科维?你可以。我不会。这里的问题是如果从多个位置调用它。首先调用它的位置是传递参数的位置。所有其他调用参数将被放到地板上并忽略。但这也是为什么当单独使用时,singelton模式是一个坏主意。您应该结合使用这个模式和一个构建器模式(比如工厂模式)。总的来说,辛格模式不是一个好主意。您可能应该考虑另一种设计应用程序的方法。
- 希望,在C++支持的C++ 03模式下,它也是线程安全的,支持C++ 11:如果在没有编译器在C++ 11模式下运行的情况下线程安全关闭,这将是最丑陋的优化,但是在C++世界中,我见过这么多丑陋的优化,如果他们这样做,它不会真的让我吃惊。
- 你是应该创建一个类Singleton,然后每次你想要创建一个单例时都派生它,还是应该在每个单例类中使用这个设计模式?
- @Kim366:这是在softwarengineering.stackexchange.com上问的一个好问题。
- @Lokiastari好吧,我会的!
- 是否可以重载括号而不是执行getInstance()?那要短得多,但你还是会发现是单件的
- @Kim366:这些不是需要评论的问题类型。这些类型的问题需要他们背后的社区以及一些人的全力支持。问一个适当的问题,让社区回答,评论应该只保留对显示的代码的评论(而不是如何使其更好或替代技术)。
- 但是当我问一个关于命名和以某种方式做事情的问题时,这个问题会因为基于意见而立即被删除!
- @如果你问一个基于意见的问题,那么是的。但是,在评论中提出基于观点的问题意味着我会给你一个基于观点的答案(没有人在看)。我尽量不回答基于意见的问题,因为没有真正的答案。
- 可以。。。够公平的
- 我只想参考stackoverflow.com/questions/39934890/…,因为它有助于解决我在错误地使用getInstance方法时遇到的特定问题。
- 我不理解代码S(S const&) = delete; void operator=(S const&) = delete;。它已经放在private里了吗?
- 此解决方案在与应用程序一起编译时工作良好。但是,如果我在一个dll中构建它并调用任何使用getInstance()的函数,则类的构造函数永远不会被调用,从而最终导致分段错误…有什么想法吗?我甚至可以使用调试器跳过getInstance(),但显然构造函数只是不被调用。
- 第一次访问时是否真的实例化了static S instance;?我怀疑它是初始化的,并且在加载应用程序时它的构造函数是在main之前调用的。我错了吗?
- @ KarolisMilie?是的,你错了。函数范围内的静态存储变量是在第一次使用时构造的。所以第一次调用函数是在构造它的时候。现在这种情况可以在主管道之前发生。如果从文件范围内另一个静态存储变量的构造函数调用getInstance()。
- @ MartinYork,你能分享任何C++标准参考吗?
- @ KarolisMilie?江户十一〔四〕中的KA。第9.7条声明声明。第4段Dynamic initialization of a **block-scope** variable with **static storage duration** or thread storage duration is performed the first time control passes through its declaration;。
- @ KarolisMilie?但是我不应该告诉你。你可以在30秒内写一个程序告诉你答案。
- 你怎么知道在调用getInstance之前instance没有被实例化?静态变量就是这样工作的吗?这意味着getInstance需要有一些隐藏的变量来告诉它是否以前被调用过。
- @是的,这就是静态存储函数变量的工作方式。它是如何工作的是实现定义的标准并没有告诉你如何使它工作,它只是告诉你代码应该如何工作。现在,您对隐藏变量的建议是一种技术,另一种技术是拥有两个入口点和由间接调用定义的入口点。我确信还有一些我没有想到的潜在的人。
- @马丁约克有两个入口是什么意思?
- @这不重要。我想说的是,它不一定意味着变量是必需的。只要行为是由标准定义的,实现者就可以使用机器特定的构造来实现。这并不意味着总的来说会有额外的费用。您需要对编译器生成的内容进行反编译,并查看它生成的内容,以了解实际的实现功能。知道这一点并不能给你提供任何其他实现的信息。
- @但是,程序如何支持知道是否应该为对定义它的函数的特定调用初始化静态变量,除非它有一些内存(即隐藏变量)来确定变量是否已经初始化?如果你有解决这个问题的方法,不使用隐藏变量,我想知道它。
- @你错过了要点。编译器实现者可以自由使用任何技术。仅仅因为你(我的意思是你在通用的"你"而不是特定的"你")不知道编译器工程师的所有技巧并不意味着这是不可能的。但是使用变量是一种非常简单和可靠的方法;我敢打赌很多实现都是这样做的。但很多并不意味着全部。
- MartinYork,我明白你的意思;C++规范并没有说明编译器应该如何实现,而只能是结果可执行文件应该具有的行为。但是,既然您说可以通过使用两个入口点而不使用隐藏变量来实现这种行为,而且我不知道如果不使用任何隐藏变量如何实现这种行为,那么我的好奇心就会被激发,我想知道您的方法是如何工作的。那么,你能解释一下怎么做吗?(是的,我意识到我在这里讨论了一些话题,但我希望没关系!)
- @我20年前就不再做编译器了。从那时起编译器和硬件都在发展,所以我的知识已经过时了。但是我为一个SoC芯片做了一个C++编译器。带有静态存储持续时间对象的函数是用多个入口点实现的。通过一些分析,我们通常可以识别其中一个函数的第一个调用者,这样这个调用者就可以到达入口点强制初始化静态存储对象。然后,其他调用将使用其他入口点(如果可以识别第一个调用方,则可以返回到标准方法)。
- @hellogoodbye是我处理的另一个系统,它保留了表中的函数调用,调用指令是该表的索引(没有直接调用)。因此,最初可以指向第一个入口点(它对静态成员进行初始化,然后更新调用表,以便后续调用使用函数的第二个入口点)。
- @我在大学攻读博士学位,为数据流处理器编写了一个编译器。这里没有记忆的概念。所有对象都存储为流过边缘的状态。我不知道它是如何在这里实现的(其他人是这样做的),但是由于没有内存或变量的概念,所以您不能记住它是否被初始化。它要么被初始化,然后流过和边缘,要么它不存在。
- @hellogoodbye已知底层硬件的知识,您通常可以发现在语言级别不可用的微优化,这些优化非常模糊,汇编程序编写人员不使用它们。但一旦解决了,就相对容易添加到编译器中(它可以在编译时快速验证所有的预条件)。
- @hellogoodbye:函数调用表是硬件保护的一部分。实际上有多个调用表。正在使用的表取决于硬件的状态。因此,您可以在硬件级别防止/捕获对非法子系统的调用,而基本上不需要对代码路径进行活动检查。从而产生更快、更小的代码。
- @马丁约克哇,这是一个彻底的解释,非常感谢!你在这方面似乎有很多知识和经验。啊,有了多个入口点,你基本上可以在编译时发现哪个调用是第一个聪明的调用,我没有想到。我猜函数调用表隐式地保留了一些关于函数是否以前被调用的记忆?
- @关于数据流处理器,因为数据流编程通常是声明性的而不是命令式的(对吗?),在这种情况下,我不确定函数中静态变量的预期行为是什么,因为节点计算的顺序以及函数调用的顺序通常是任意的,对吗?如果是这样,静态变量是否会使行为未定义,因为它依赖于函数调用的顺序?
- @hellogoodbye数据流编程可能是声明式编程风格,但数据流架构有指令。我用的是曼彻斯特数据流计算机。将C转换为if1,然后对其进行优化,然后转换为直接实现数据流指令集的数据流图。我在那里的时候,我们实现了大部分功能。我正在进行优化,试图在所有参数可用之前启动函数。
- @我明白了。谢谢!
作为一个独身者,你通常不希望它被破坏。
当程序终止时,它将被拆散和解除分配,这是单例程序所需要的正常行为。如果您希望能够显式地清除它,那么向类中添加一个静态方法就相当容易了,该方法允许您将其恢复到干净状态,并在下次使用时重新分配它,但这超出了"经典"单例的范围。
- 我正要点击"发帖",因为你的答案和我的几乎一样。
- 如果从未在静态singleton*实例上显式调用delete,从技术上讲,这是否仍然被视为内存泄漏?
- 但是,如果我打开了一些系统资源并想在完成之前释放它们,我需要做的是,我也希望它自动发生。我想到的解决方案是在这里采用智能指针。
- 是的,但从设计上看,这是一种内存泄漏。这就是为什么许多人觉得单例有一种代码味道的原因之一——他们故意违反规则。它们实际上只是封装全局变量的一种方法,比封装全局变量要干净一点,但是针对全局变量的相同论点对于单例变量也是正确的,包括非确定性清理。请看我关于用清理扩展它的说明-如果在应用程序拆卸期间调用了它(即:删除了静态),那么您将调用析构函数,并避免这种情况。
- 它不再是内存泄漏,而是一个全局变量的简单声明。
- 如果您真的需要,可以使用raii习语删除singleton。更好的是,imho,是使用C描述的迈尔斯单例模式吗?T?林丕体?.
- @IlyaN.Memory被分配了,但没有释放内存泄漏,这与变量的作用域无关。
- 如果您想释放资源,我怀疑singleton可能不是这里的正确工具。无论如何,我不明白为什么你不能创建一个"destroy()"方法来释放内存并将指针设置为空。接下来调用getInstance()只会重新分配内存。
- @尼尔,你能给我一份推荐信吗?
- 把事情弄清楚……""记忆泄漏"对单子的关注是完全不相关的。如果您有状态资源,解构顺序很重要,那么单例可能是危险的;但是在程序终止时,操作系统会干净地恢复所有内存…在99.9%的案件中取消这一学术观点。如果你想反复论证什么是和什么不是"内存泄漏"的语法,那没关系,但是要意识到它分散了实际设计决策的注意力。
- @阿耳特姆-我能……看到这条线:)
- JCKIAN:C++上下文中的内存泄漏和破坏不是真正的内存泄漏。实际上是关于资源控制的。如果泄漏内存,则不会调用Destroctor,因此与对象关联的任何资源都不会正确释放。内存只是我们在教授编程时使用的一个简单的例子,但是那里有很多更复杂的资源。
- @马丁,我完全同意你的看法。即使唯一的资源是内存,如果你必须通过一个泄漏列表,过滤掉那些"无关紧要"的,你仍然会遇到在程序中寻找真正的泄漏的麻烦。最好把这些都清理干净,这样任何报告泄漏的工具只报告有问题的东西。
- 模糊地考虑到存在C++实现(甚至可能是托管的),其中"OS"在您的程序退出时不会恢复所有资源,但它确实有一些"再次运行程序"的概念,这给了您一组新的全局和静态本地。在这样的系统上,根据任何合理的定义,一个未释放的单例是一个真正的泄漏:如果您的程序运行了足够的时间,它将导致系统崩溃。你是否关心这样的系统的可移植性是另一回事——只要你不写图书馆,你几乎肯定不会写。
- @ SteveJessop,您介意提供这样一个C++实现或操作系统的例子,该系统不会在程序出口上回收资源吗?我不知道我听过这样的事,我很感兴趣。也许你在引用某种RTO?
- @戴维弗瑞实际上这是几个领域的共同情况。例如,Windows内核驱动程序在独立环境中运行,即终止时不会检索资源。几乎所有的嵌入式软件都是如此。
您可以避免内存分配。有许多变体,在多线程环境中都有问题。
我更喜欢这种实现(实际上,它没有正确地说我更喜欢,因为我尽量避免使用单例):
1 2 3 4 5 6 7 8 9 10 11 12
| class Singleton
{
private:
Singleton();
public:
static Singleton& instance()
{
static Singleton INSTANCE;
return INSTANCE;
}
}; |
它没有动态内存分配。
- 在某些情况下,这种懒惰的初始化不是理想的模式。一个例子是,如果singleton的构造函数从堆中分配内存,并且您希望这种分配是可预测的,例如在嵌入式系统或其他严格控制的环境中。当单例模式是最好使用的模式时,我更喜欢将实例创建为类的静态成员。
- 对于许多较大的程序,尤其是那些具有动态库的程序。任何非原语的全局或静态对象都可能在许多平台上的程序退出时导致segfaults/crash,这是由于库卸载时的破坏顺序问题。这是许多编码约定(包括谷歌)禁止使用非平凡的静态和全局对象的原因之一。
- 这种实现中的静态实例似乎有内部链接,在不同的翻译单元中会有唯一的、独立的副本,这会导致混乱和错误的行为。但我看到了很多这样的实现,我是否遗漏了一些东西?
@洛基·阿斯塔里的回答很好。
但是,在有多个静态对象的情况下,您需要确保在所有使用该单例的静态对象不再需要它之前,不会销毁该单例。
在这种情况下,即使在程序结束时调用静态析构函数,也可以使用std::shared_ptr为所有用户保持singleton活动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Singleton
{
public:
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
static std::shared_ptr<Singleton> instance()
{
static std::shared_ptr<Singleton> s{new Singleton};
return s;
}
private:
Singleton() {}
}; |
另一种非分配的选择是:根据需要创建一个单例,比如类C:
使用
1 2 3 4 5 6
| template <class X>
X& singleton()
{
static X x;
return x;
} |
这也不是C?T?林的答案是自动线程安全在当前的C++,但将在C++0X。
- 目前在GCC下,它是线程安全的(并且已经有一段时间了)。
- 这种设计的问题是,如果在多个库中使用的话。每个库都有它所使用的单例的副本。所以它不再是单身。
接受的答案中的解决方案有一个显著的缺点——在控件离开main()函数后调用单例的析构函数。当一些依赖对象在main中分配时,可能确实存在问题。
我在Qt应用程序中引入单例时遇到了这个问题。我决定,所有的设置对话框都必须是单例的,并采用上面的模式。不幸的是,qt的主类QApplication是在main函数的堆栈上分配的,qt禁止在没有应用程序对象可用时创建/销毁对话框。
这就是为什么我更喜欢堆分配的单例。我为所有的单例提供了一个明确的init()和term()方法,并在main中调用它们。因此,我完全控制了单件物品的制造/销毁顺序,并且我保证无论是否有人称为getInstance(),单件物品都将被制造出来。
- 你好像用错了。
- 如果你指的是目前接受的答案,你的第一句话是错误的。在销毁所有静态存储持续时间对象之前,不会调用析构函数。
如果要在堆中分配对象,为什么不使用唯一指针。内存也将被释放,因为我们使用的是唯一的指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class S
{
public:
static S& getInstance()
{
if( m_s.get() == 0 )
{
m_s.reset( new S() );
}
return *m_s;
}
private:
static std::unique_ptr<S> m_s;
S();
S(S const&); // Don't Implement
void operator=(S const&); // Don't implement
};
std::unique_ptr<S> S::m_s(0); |
- 在C++ 11中被弃用。建议使用唯一的指针。cplusplus.com/reference/memory/auto_ptr cplusplus.com/reference/memory/unique_ptr
- 这不是线程安全的。最好让m_s成为getInstance()的本地static并在不进行测试的情况下立即初始化。
这是一个简单的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <Windows.h>
#include <iostream>
using namespace std;
class SingletonClass {
public:
static SingletonClass* getInstance() {
return (!m_instanceSingleton) ?
m_instanceSingleton = new SingletonClass :
m_instanceSingleton;
}
private:
// private constructor and destructor
SingletonClass() { cout <<"SingletonClass instance created!
"; }
~SingletonClass() {}
// private copy constructor and assignment operator
SingletonClass(const SingletonClass&);
SingletonClass& operator=(const SingletonClass&);
static SingletonClass *m_instanceSingleton;
};
SingletonClass* SingletonClass::m_instanceSingleton = nullptr;
int main(int argc, const char * argv[]) {
SingletonClass *singleton;
singleton = singleton->getInstance();
cout << singleton << endl;
// Another object gets the reference of the first object!
SingletonClass *anotherSingleton;
anotherSingleton = anotherSingleton->getInstance();
cout << anotherSingleton << endl;
Sleep(5000);
return 0;
} |
只创建了一个对象,并且每次在单词后都返回此对象引用。
1 2 3
| SingletonClass instance created!
00915CB8
00915CB8 |
这里,00915cb8是singleton对象的内存位置,在程序运行期间相同,但(通常!)每次运行程序时都不同。
注意,这不是线程安全的。您必须确保线程安全。
我在答案中没有找到CRTP实现,所以这里是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| template<typename HeirT>
class Singleton
{
public:
Singleton() = delete;
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
static HeirT &instance()
{
static HeirT instance;
return instance;
}
}; |
从中继承你的类,比如:class Test : public Singleton。
它实际上可能是从堆中分配的,但是如果没有源,就无法知道。
典型的实现(取自我在Emacs中已有的一些代码)是:
1 2 3 4 5 6
| Singleton * Singleton::getInstance() {
if (!instance) {
instance = new Singleton();
};
return instance;
}; |
…然后依靠超出范围的程序进行清理。
如果您在一个必须手动进行清理的平台上工作,我可能会添加一个手动清理例程。
这样做的另一个问题是它不是线程安全的。在多线程环境中,两个线程都可以在其中一个有机会分配新实例之前通过"if"(如果)。如果你仍然依赖程序终止来清理,这还不算什么大不了的事情。
- 您可以推断,因为您可以看到实例变量是指向类实例的指针。
- 不需要动态分配单例。事实上,这是一个坏主意,因为没有办法使用上述设计自动取消分配。让它超出范围是不调用析构函数而只是懒惰。
- 您可以使用ATEXIT函数自动解除分配。我们就是这么做的(不是说这是个好主意)
有人提到过std::call_once和std::once_flag吗?大多数其他方法——包括双重检查锁定——都被破坏了。
单例模式实现中的一个主要问题是安全初始化。唯一安全的方法是用同步屏障保护初始化序列。但这些障碍本身需要安全地启动。std::once_flag是保证安全初始化的机制。
除了这里的其他讨论之外,可能值得注意的是,您可以拥有全局性,而不局限于一个实例。例如,考虑引用计数的情况…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| struct Store{
std::array<Something, 1024> data;
size_t get(size_t idx){ /* ... */ }
void incr_ref(size_t idx){ /* ... */}
void decr_ref(size_t idx){ /* ... */}
};
template<Store* store_p>
struct ItemRef{
size_t idx;
auto get(){ return store_p->get(idx); };
ItemRef() { store_p->incr_ref(idx); };
~ItemRef() { store_p->decr_ref(idx); };
};
Store store1_g;
Store store2_g; // we don't restrict the number of global Store instances |
现在,在函数(如main中)的某个地方,可以执行以下操作:
1 2
| auto ref1_a = ItemRef<&store1_g>(101);
auto ref2_a = ItemRef<&store2_g>(201); |
引用不需要将指针存储回它们各自的Store,因为这些信息是在编译时提供的。您也不必担心Store的生存期,因为编译器要求它是全局的。如果确实只有一个EDOCX1的实例(1),那么这种方法就没有开销;如果有多个实例,那么编译器就应该对代码生成有足够的了解。如有必要,甚至可以把ItemRef类变成Store的friend(你可以有模板化的朋友!).
如果Store本身是一个模板化的类,那么事情会变得更混乱,但是仍然可以使用这个方法,可能通过实现具有以下签名的助手类:
1 2 3
| template <typename Store_t, Store_t* store_p>
struct StoreWrapper{ /* stuff to access store_p, e.g. methods returning
instances of ItemRef<Store_t, store_p>. */ }; |
用户现在可以为每个全局Store实例创建一个StoreWrapper类型(和全局实例),并始终通过其包装实例访问存储(从而忘记使用Store所需的模板参数的详细信息)。
这是关于对象生命周期管理的。假设您的软件中有多个单例。他们依赖于伐木工人辛格尔顿。在应用程序销毁期间,假设另一个singleton对象使用logger记录其销毁步骤。你必须保证日志记录应该最后清理。因此,请查看本文:http://www.cs.wustl.edu/~schmidt/pdf/objman.pdf
上面链接的文章描述了双重检查锁定的缺点,即在调用对象的构造函数之前,编译器可以为对象分配内存,并设置指向分配内存地址的指针。然而,C++中很容易使用分配器来手动分配内存,然后使用构造调用初始化内存。使用这个appraoch,双重检查锁定工作正常。
- 不幸的是,没有。这已经被一些最好的C++开发人员在很大程度上讨论过了。在C++ 03中双检查锁定被破坏。
1
| #define INS(c) private:void operator=(c const&){};public:static c& I(){static c _instance;return _instance;} |
例子:
1 2 3 4 5 6 7 8
| class CCtrl
{
private:
CCtrl(void);
virtual ~CCtrl(void);
public:
INS(CCtrl); |
简单的singleton类,这必须是头类文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #ifndef SC_SINGLETON_CLASS_H
#define SC_SINGLETON_CLASS_H
class SingletonClass
{
public:
static SingletonClass* Instance()
{
static SingletonClass* instance = new SingletonClass();
return instance;
}
void Relocate(int X, int Y, int Z);
private:
SingletonClass();
~SingletonClass();
};
#define sSingletonClass SingletonClass::Instance()
#endif |
像这样访问您的单身汉:
1
| sSingletonClass->Relocate(1, 2, 5); |
我认为您应该编写一个静态函数,其中删除静态对象。您应该在即将关闭应用程序时调用此函数。这将确保您没有内存泄漏。
如何使用像这样的新位置:
1 2 3 4 5 6 7 8 9 10 11 12 13
| class singleton
{
static singleton *s;
static unsigned char *buffer[sizeof(singleton)/4 *4] //4 byte align
static singleton* getinstance()
{
if (s == null)
{
s = new(buffer) singleton;
}
return s;
}
}; |
- 为什么。这似乎很复杂。而析构函数从未被调用。另外,您还需要修正表达式来计算当前的大小,它将平均在25%的时间内工作(尝试size of(singelton)=3的情况。然后上面的表达式将生成0大小的数组。