曾几何时(7 年前)我读过 Alexandrescu。我注意到他花了很多空间来创建单例。也就是说,这种类型的对象存在于程序中的单个实例中。
问题:
为什么我们需要这样的对象存在于程序中的单个副本中?
在程序中愚蠢地创建一次指定类型的对象而不再次创建这种类型的对象有什么问题?
任何类的静态成员都存在于程序中的单个实例中。这将检查翻译器和链接器,并在尝试创建静态类成员的第二个实例时引发错误。为什么任何类的静态成员都不是单例?
UPD1:
[@pepsicoca1,用代码解释问题的第三部分......你的意思并不完全清楚。]
迫于压力,我发布了构建一个以我命名的单身人士的秘密协议(我没有广播这个例子,这是一个即兴创作):
class shell_singleton;
clacc singleton{
friend shell_singleton;
private:
void method1(){/*some action*/}
void method2(int a){/*some action*/}
void method3(int a,float b){/*some action*/}
public:
};
class shell_singleton{
static singleton singleton_var;
public:
void method1(){singleton_var.method1();}
void method2(int a){singleton_var.method2(a);}
void method3(int a,float b){singleton_var.method3(a,b);}
};
singleton shell_singleton::singleton_var;
void main(){
shell_singleton shell_singleton_var;
shell_singleton_var.method1();
shell_singleton_var.method2(3);
shell_singleton_var.method3(5,10.1);
}
在这个例子中,shell_singleton::singleton_var 的一个实例;不能再次创建。
试图创建 shell_singleton::singleton_var; 将导致编译或链接错误。
不需要类实例化计数器。
一切都通过编译器和链接器解决。
同时,即使您创建了单例类型的实例,也无法使用它,因为它的所有方法都是私有的。
但是我玩得很安全,用户更容易根本不宣布单例类的存在,让他们使用 shell_singleton 类。
[@pepsicoca1,用代码解释问题的第三部分
你对前两部分有什么想法吗?然后由于某种原因,所有的大师都保持沉默,你可以看到没有人构建单例,亚历山德雷斯库在这个话题上被钉死在十字架上是徒劳的。无关主题可看。
其实我在另一个地方问过这个问题。这是查看讨论的链接:
http://www.cyberforum.ru/cpp-beginners/thread2205986.html
在那里,与stackoverflow不同,有几个答案。我不会说我的同事的论点使我相信构建单例的重要性。但至少是一些东西,至少是一些版本。
UPD2:
我在闲暇时想了想。好吧,程序员创建了一个单例。类型保护自己免受可怕的用户。用户获取并创建了一个 DLL,并在此 DLL 中创建了单例类型的第二个实例。没有编译器会跟踪这一点,因为 DLL 是单独翻译和链接的。然后你用单身人士为自己辩护的是什么?
引用已经提到的“设计模式”:
推:
现在的问题:
与所有模式一样,单例只是一种工具,如果用于其预期目的,则可以使生活更轻松。所以答案就是为什么——因为这样做更容易,而且在这种特殊情况下,这种解决方案比替代方案(全局变量和函数)更好。为什么会发生这种情况——因为在实施过程中,一些经理、一些工厂、一些饲养员会简单地弹出,从逻辑上讲,它们不应超过一个,但需要从完全不相关的代码部分访问。
想到的第一个例子是各种框架中的 Application 对象。
创建一个对象不是问题,但是从程序中的任意位置获取到它的链接是一个问题(参见上面的目的),它的单例只是解决了它。
事实上,该解决方案具有生命权,在某些抽象情况下它可能更可取,但与通常的单例相比,我没有看到任何优点,但从缺点来看:
不可能通过创建子类来扩展。
需要不与单例本身进行通信,而是创建一个特定的子类并通过基接口与其通信,那么结构是这样的:
如果单例是抽象工厂、桥的背面或策略,通常会出现这种情况。
用户没有能力以某种方式影响对象的创建。
特别是,将某些东西传递给
create(),如上面的示例所示,如果不需要,则根本不可能不创建单例,也不可能进行延迟初始化等。如果有多个交互的单例,那么很难指定它们的创建顺序。
让我们首先记住什么是单例。单例是一种在程序生命周期内只能有一个实例的类型。经典书籍Design Patterns将单例描述为一个没有公共构造函数和静态方法的类
get_instance()。在更广泛的意义上,任何单实例类都可以称为单例,而不管实现如何。现在回答你的问题。
很简单。类应该模拟现实的对象。例如,如果你写一个描述一个星球的程序,那么这个星球是程序内唯一的一个,用单例来描述它很方便。如果你正在编写一个控制枪的程序,那么程序中枪的实例是相当单例的。
是的,您可以只同意自己的观点,而不创建该类的其他实例。
这是正常的,直到第二、第三、第四个开发人员出现在您的项目中。他们看到了这个类,但他们不知道或者只是没有记住应该只有一个实例。所以他们悄悄地创建了另一个实例,结果,每微秒在你的太阳系中循环生成一个太阳。
请记住,人们会犯错误。编译器发现的错误的价格,不要让我们编译错误的代码,远低于测试人员或(更糟!)客户发现的错误的价格。
您应该始终假设,如果编译器不禁止某些事情,开发人员会尝试这样做。顺便说一句,如果您在暂停六个月后返回完成此项目,那么这个开发人员可以是您自己。因此,如果可以强制编译器遵循某些规则的执行,那么应该这样做。
静态成员不是单例,因为它不是一个只能实例化一次的类。也就是说,根据单例的定义。在更广泛的意义上,静态成员可以被认为是单例的一种替代,但它有一个缺点:程序员可以替换位于静态字段中的对象的实例。这正是单身人士不可能做到的。
对于我自己,我认为需要像这样使用这种模式:
如果你想编写一些需要在程序的不同部分使用的功能——例如,资源加载器,那么你可以使用类的静态函数。
但是,如果您想在资源加载器的某个其他类中使用定义的功能,那么您可能需要使用继承。然后你将不得不
ResourceLoader从一些具有必要功能的类继承。并且为了确保程序中只有一个资源加载器实例,单例将帮助您。(例如,为了防止资源竞争)。单例的另一个重要特性是能够延迟初始化整个事情。您可能永远不需要程序中的资源加载器。然后你不会引用它并且不会创建加载器实例,不会再次使用处理器资源(突然这个类在创建过程中索引整个文件系统,这并不快)。
至于特殊关键字 -
C++里面没有。仅仅因为单例已经是一种设计模式,比C++. 然而,在其他一些更高级的语言(如KotlinorScala)中根本没有静态成员的概念,等效的功能是使用伴生对象实现的,它们是单例。是的,它们可以被继承!类似的东西。当然,程序员自己选择什么以及如何使用它。但是,正如@VladD 所说,当您可以让编译器为您检查所有内容时,为什么还要在脑海中保留一些东西呢?