序言
-
比较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为什么能防止类的实例化
-
- 私有构造函数无法在类的外部被访问:私有构造函数只能在类的内部被调用,无法在类的外部被访问和调用。因此,无法通过在类的外部实例化对象来创建类的实例
-
- 继承关系下的限制:如果将构造函数定义为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单例模式文章来源:https://www.toymoban.com/news/detail-826875.html
created by shuaixio, 2024.02.18文章来源地址https://www.toymoban.com/news/detail-826875.html
到了这里,关于【c/c++】C++静态工具类和单例模式对比学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!