如何在 C++ 中创建单例类

17 Mar 2025 | 6 分钟阅读

什么是单例类?

C++ 中的单例类是一种设计模式,它确保一个类只有一个实例,并提供对该实例的全局访问点。它限制了一个类可以生成的对象数量,因为在程序的整个生命周期中,只能使用一个对象来访问该类。当您希望确保只创建一个类的实例时,此技术对于控制实例创建非常有用。

在 C++ 中创建单例类的步骤

在 C++ 中创建单例类的步骤如下:

步骤 1:私有构造函数

单例类应定义一个私有的构造函数。

这可以防止外部程序直接创建实例。

步骤 2:私有静态成员

创建一个私有且静态的类成员来保存单个实例。

此成员将保存类的单个实例。

步骤 3:公共静态方法 (getInstance())

创建公共静态函数 getInstance() 以授予对对象的访问权限。

此方法负责生成实例并返回它。

步骤 4:静态成员初始化

将静态实例成员的初始值设置为 nullptr 或类似值。

步骤 5:实例创建

在 getInstance() 函数中检查实例是否首次创建(nullptr)。

如果不是,则使用私有构造函数创建一个全新的实例。

步骤 6:线程安全(可选)

如有必要,请使用线程安全措施。

您可以使用 std::call_once 或双重检查锁定等方法。

步骤 7:返回实例

应从 getInstance() 函数返回实例。

步骤 8:实现单例

使用 getInstance() 方法访问单例实例,并在代码中使用其功能。

示例

输出

How to Create a Singleton Class in C++

说明

  • 此 C++ 代码实现了 Singleton 模式。Singleton 模式提供类的单个实例,还提供对该实例的全局访问点。代码的运行方式如下:
  • 创建 Singleton 类,它有一个名为 uniqueInstance 的私有静态成员,用于存储类的单个实例。私有构造函数可防止外部实例化。
  • Singleton 类将 getInstance() 函数定义为静态成员。使用此技术,您可以从任何地方获取单个实例。如果实例已存在,则函数会进行检查(uniqueInstance == nullptr)。如果不存在,则通过私有构造函数创建新实例来实现延迟初始化。但是,它会返回实例。
  • Singleton 类中的 showMessage() 函数仅输出一条消息,说明单例实例已成功创建。
  • uniqueInstance 是一个静态成员,初始化为 nullptr,位于类外部。
  • main() 方法中可以看到 Singleton 模式的应用。在调用 Singleton::getInstance() 之后,声明了两个指针 instance1 和 instance2,并分别赋予了它们的值。这确保了两个指针指向的实例是相同的,从而满足了单例条件。
  • 通过使用 instance1->showMessage() 和 instance2->showMessage() 来打印单例的消息。由于两个指针都指向同一个实例,因此消息只打印一次。

单例类的特性和优点

  • 单个实例:单例模式提供的单个实例确定性是其主要优点。当您想防止实例重复并管理特定对象的生成时,这非常有用。
  • 全局访问:得益于单例,可以从世界上的任何地方访问单个实例。这使得程序的其他组件可以在不显式传递的情况下访问实例。
  • 资源管理:单例可用于管理应用程序整体必须共享的资源。例如,单例可用于维护数据库连接、文件句柄或网络套接字,确保有效的资源利用。
  • 集中配置:当一个类负责控制配置选项或应用程序参数时,使用单例可确保应用程序的所有组件都能一致地访问相同配置数据。
  • 延迟初始化:单例设计支持延迟初始化,这意味着仅在首次请求实例时才创建实例。这对于减少内存使用量和提高速度很有益,特别是对于需要大量资源的组件。
  • 避免使用全局变量:单例提供了对全局变量的受控替代方案,全局变量容易出错,并可能导致应用程序不同组件之间意外的交互。
  • 一致的状态:单例确保实例的状态在应用程序的整个执行过程中保持一致,因为它强制程序的单个实例。
  • 线程安全(实现时):当单例旨在实现线程安全时,它可以允许并发访问实例,防止数据竞争和死锁等问题,并确保在多线程环境中进行适当的同步。
  • 内存管理:单例提供了对象创建和销毁的唯一点,可以帮助管理内存分配和释放。
  • 易于实现:遵循一套清晰的说明,可以轻松实现单例模式。其简单性使其易于被接受和推广。

C++ 中单例类的缺点

  • 全局状态:单例在其实例上创建了全局访问点。这可能导致程序的全局状态,使得跟踪依赖项和理解代码各个部分如何交互变得更加困难。对单例状态的修改可能会影响程序的其他区域,从而导致意外行为。
  • 紧密耦合:使用单例时,类经常完全依赖于单例实例。由于类之间可能存在紧密耦合,因此软件可能变得不那么模块化,并且更难重构。对单例的行为或接口的修改可能会对代码库产生重大影响。
  • 测试问题:测试使用单例的程序可能很困难。由于多个应用程序组件共享单例实例,因此单例实例使得隔离和测试特定组件变得更加困难。为测试目的模拟或替换单例实例可能很困难,这对单元测试过程有影响。
  • 并发问题:如果缺乏足够的同步措施,多线程环境中的单例实例可能会导致并发问题。当单例实例并发访问时,如果不是为线程安全设计的,则可能出现数据竞争、死锁和行为不一致。
  • 隐藏的依赖项:在类中,单例设计会隐藏依赖项。这可能会使识别依赖于单例实例的组件更加困难。当需要将单例替换为不同的实现时,很难找到受影响的所有代码。
  • 延迟实例创建:延迟初始化可能有助于节省内存资源,但在首次使用 getInstance() 函数时,它可能会导致实例创建出现意外的延迟。在某些情况下,这可能会影响应用程序的性能。
  • 违反单一职责原则:单例类通常执行多项任务,包括管理全局状态和创建新实例。如果违反了单一职责原则,该类可能会变得不那么内聚,并且更难维护。
  • 难以继承:继承单例类可能很困难。继承需要添加复杂的继承结构或修改 getInstance() 函数,从而使代码的灵活性降低且更难维护。
  • 替代模式:完全依赖单例模式可能会使您错过一些替代设计模式的优势,这些模式提供类似的功能,同时解决了前面提到的一些缺点。

结论

总而言之,C++ 单例设计提供了一种受控机制来生成类的单个实例并确保其全局可访问性。虽然它具有许多优点,例如有效的资源管理、集中配置和全球访问,但也存在一些重大缺点,包括全局状态、紧密耦合和测试困难。

单例模式应成为一个设计决策,它补充了软件系统的整体架构和设计理念。了解其优点和局限性,开发人员可以做出更明智的决策,并构建更健壮、可维护和适应性强的代码库。