【c/c++】C++静态工具类和单例模式对比学习

文章目录

        • 序言
        • 1. static静态成员
        • 2. C++(伪)静态工具类
        • 3. 单例模式
          • 3.1 单例模式的特点
          • 3.2 单例模式的实现方式
          • 3.3 单例模式的缺点
          • 3.4 Meyer Singleton单例模式
        • 4. (伪)静态工具类 vs 单例模式
          • 4.1 区别
          • 4.2 如何选择
          • 4.3 一些释疑

序言
  • 比较C++ static(伪)静态(工具)类和单例模式的异同,方便工作中正确选用实现方式

  • 说明:Java/C#等高级语言支持静态类,C++不直接支持静态类的概念,但是可以通过一些技巧来实现类似的功能:仅包含静态成员 + 私有构造函数防止类实例化,所以这里称其为伪静态类

1. static静态成员
  • 说明2:C++静态成员和静态函数

    • (1) static修饰成员变量,即为静态成员变量;修改成员方法,即为静态成员方法
    • (2) 静态成员存储在全局变量区,属于类本身,不属于对象;可通过类访问,也可通过实例化后的对象访问;因此静态成员不能在构造函数中初始化,因为构造函数是用来构造单个对象的,而静态成员属于类;也不能用初始化列表来初始化
    • (3) 静态成员在类外初始化时分配内存,程序结束时释放,等同于全局变量
      静态局部变量作用域仅限于函数内部,别的函数不能访问;
      静态全局变量作用域仅限于定义它的源文件,而不是所有源文件
    • (4) 静态成员变量在对象中不占用存储空间
    • (5) 静态方法中只能访问静态成员变量和方法;如果确实需要访问非静态成员,应该通过函数传参方式
  • 静态成员变量只是表面上遵守面向对象,在类中可通过对象调用;一定程度上破坏了面向对象,因为没对象用类名也能直接调用静态成员;静态变量可以看做类外的全局变量和全局函数被封装在了类内部,与非静态变量和成员区别很大

2. C++(伪)静态工具类
  • 静态工具类

    • (1) class声明时使用static,整个类是静态类;
    • (2) 静态类内部全是静态成员,没有非静态成员;
    • (3) 静态类的成员不能有protected或protected internal访问保护修饰符;可以有public、private限制符
    • (4) 静态类不能被实例化,因为不用实例化非静态成员;C++中私有构造函数可以防止类的实例化;静态类存储在全局变量区,生存周期和程序一致
    • (5) 静态类的初始化在类外进行,前面不加static,以免与外部静态变量相混淆;也不能使用this关键字,因为已经实例化并开辟了内存;初始化时不加访问限制符private、public等
    • (6) 静态类不能包含构造函数,但仍可声明静态构造函数以分配初始值或设置某个静态状态;
    • (7) 静态类不能指定任何接口实现,不能有任何实例成员,不能使用abstract或sealed修饰符
    • (8) 静态类成员/函数通过类名::进行访问和调用,不通过对象就可以调用
    • (9) 静态类是密封的,不能被继承,不能拿来做父类
  • C++中构造函数定义为private为什么能防止类的实例化

      1. 私有构造函数无法在类的外部被访问:私有构造函数只能在类的内部被调用,无法在类的外部被访问和调用。因此,无法通过在类的外部实例化对象来创建类的实例
      1. 继承关系下的限制:如果将构造函数定义为protected,子类可以调用父类的protected构造函数来创建父类的实例。但是,如果构造函数是private,子类无法调用父类的private构造函数,因此无法创建父类的实例
  • C++(伪)静态类的实现

头文件 xxx.h

class MapUtil {
// 所有成员均为static
public:
	static const HdMap& HdMap();
	static const PercepMap& PercepMap();
	static const FusionMap& FusionMap();

private:
	static std::unique_ptr<HdMap> hdmap_;
	static std::mutex hdmap_mutex_;

	static std::unique_ptr<PercepMap> percep_map_;
	static std::mutex percep_map_mutex_;

	static std::unique_ptr<FusionMap> fusion_map_;
	static std::mutex fusion_map_mutex_;

private:					// 不能有protected访问修饰符
	MapUtil() = delete;		// 私有构造函数防止实例化,静态成员不属于任何对象
};

源文件 xxx.cc

// 不加static也不加访问限制符
std::unique_ptr<HdMap> MapUtil::hdmap_ = nullptr;
std::mutex MapUtil ::hdmap_mutex_;

const HdMap& MapUtil::HdMap()
{
	std::lock_guard<std::mutex> lock(hdmap_mutex_);
	if (hdmap_ != nullptr) {
	return *(hdmap_.get());		// 不推荐这样使用
} else {
	hdmap_ = CreateHdMap();
}
return *(hdmap_.get());
}
3. 单例模式
  • 单例模式设计模式中最基础最简单的一种,也是C++中常见的模式之一
  • 这里重点介绍Meyer单例
3.1 单例模式的特点
  • (1) 一个类只有一个实例
  • (2) 该实例在运行周期内始终存在
  • (3) 该实例可以被全局访问
3.2 单例模式的实现方式
  • (1)饿汉式单例模式
    • 也称为静态单例模式;
    • 程序运行之前就创建,因此是线程安全的;
    • 构造析构函数私有,防止外部创建对象,通过puclic getinstance访问
  • (2)懒汉式单例模式和双重检查锁懒汉式单例模式
    • 在getInstance被调用时才会创建单例对象;
    • 普通懒汉式单例模式,线程不安全;
    • 改进懒汉式单例模式,加双重检查锁,保证线程安全、避免资源浪费
  • (3)Meyer单例模式
    • 利用C++ 11中local static变量的线程安全特性,因此是线程安全的;
    • 构造析构函数私有,不能外部创建对象,通过puclic getinstance访问
    • 在第一次调用时才创建单例,类似懒汉模式,此后每次获取都返回同一实例;
    • 实例内静态局部变量在程序生命周期内只会被创建一次,具有线程安全性,不需加锁
3.3 单例模式的缺点
  • (1)单例模式的代码比较复杂,可能需要在多线程环境下使用同步锁等机制,以保证单例对象的唯一性和线程安全性
  • (2) 单例对象在整个应用程序的生命周期内都存在于内存中,可能会占用较多的系统资源,特别是在单例对象比较庞大或需要长时间运行的情况下;
  • (3)单例模式在某种程度上违反了面向对象设计的一些原则,例如开闭原则、依赖倒置原则等,因为它将对象的创建和使用耦合在一起,不太容易进行单元测试、模块化开发等;
  • 单例模式在一些场景下非常有用,但也不能滥用单例模式,需要确保它是最优解决方案
3.4 Meyer Singleton单例模式
class Singleton
{
public:
    static Singleton& Instance()
{
	/* 静态局部变量,第一次调用时初始化,全局生命周期 */
        static Singleton instance;
        return instance;
    }
    Singleton(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton& operator=(Singleton&&) = delete;
private:
    Singleton() = default;
    ~Singleton() = default;
};
  • 不暴露单例的构造析构函数,保证单例类不会通过其他途径被实例化
  • 禁用单例类的拷贝构造、移动构造和赋值构造函数,防止类的唯一实例被拷贝或移动
4. (伪)静态工具类 vs 单例模式
4.1 区别
  • (1) 实例:

    • 静态类没有实例,所有成员都是静态的,不需要实例化;
    • 单例有唯一实例,提供成员的访问接口
  • (2) 初始化:

    • 静态类在第一次加载时初始化;
    • 静态类如懒汉模式可以延迟初始化
  • (3) 扩展性:

    • 静态类通常不能扩展
    • 单例类可以实现接口、继承或者其他使用方法扩展,接口可覆写
  • (4) 全局访问:

    • 静态类通过类名直接访问成员;
    • 单例类提供全局访问点,以在整个程序中共享状态
  • (5) 访问效率:

    • 静态类被认为有更好的访问效率;
  • (6) 可测试性:

    • 单例模式更容易测试
  • (7) 状态维护:

    • 单例模式更方便于维护状态,如果不需要维护任何状态,则适合用静态类;
    • 在一些框架中,单例对象更好管理
  • (8) 面向对象

    • 单例模式比静态类更加面向对象,单例可以使用继承和多态,继承基类,实现自己的接口,提供不同的功能,返回不同的实现对象
4.2 如何选择
  • (1) 静态类适合一些工具类xxx_Util,如地图类MapUtil()
  • (2) 如果单例不维护任何状态,与实例对象无关,不考虑继承和多态,只提供全局访问,适合用静态类
  • (3) 从线程安全、性能、兼容性上来看,也是选用实例化方法为宜
4.3 一些释疑
  • (1) 静态类常驻内存,单例对象不是,所以静态效率高但占内存,类似空间换时间
    • 不是。首次加载后都常驻内存,都放在method table中,效率区别很小,静态类并没有很高效
  • (2) 静态类在堆上分配内存,单例在栈上分配内存
    • 不是。静态成员存储在全局变量区,单例类的对象都有自己的存储区域,普通成员变量存储在栈区
  • (3) 单例要先创建再使用,静态类直接使用,所以静态类更简单
    • 不是。单例的引入是为了更模式化、更面向对象化,单例和静态类的区分是为了解决模式问题,如“人类”和单个人。如果确实需要使用实例,那创建实例对象就是必须的,没有麻烦简单一说,如何选择见5.2所示

 


【参考文章】
[1]. C++静态类实现
[2]. C++中的静态成员
[3]. C++静态成员和静态类
[4]. C++静态成员和静态类,推荐
[5]. C++静态类
[6]. C++单例模式
[7]. C++单例模式介绍,推荐
[8]. 静态变量
[9]. Meyer单例
[10]. Meyer单例,推荐
[11]. 静态类vs单例模式
[12]. 静态类vs单例模式,推荐
[13]. 静态类 vs单例模式

created by shuaixio, 2024.02.18

你可能感兴趣的:(C/C++,c++,静态工具类,单例模式,Meyer单例,静态类和单例模式的选择)