C++之特殊类设计

发布于:2025-09-15 ⋅ 阅读:(17) ⋅ 点赞:(0)


前言

今天我们一起来学习常见的特殊类怎么设计,以及了解什么是单例~


一、 设计一个不能被拷贝的类

拷贝发生在两种场景:拷贝构造函数和赋值运算符。因此,若要禁止拷贝,只需让该类不能调用这两个函数。

1. C++98 实现方式

  • 方法:将拷贝构造函数和赋值运算符 仅声明不定义,并设置为 private
  • 原因
    1. 私有化拷贝构造和赋值运算符,防止外部访问。
    2. 仅声明不定义,确保该函数不会被调用,即使成员函数内部尝试拷贝也会报错。
class CopyBan {
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
};

2. C++11 实现方式

  • C++11 提供 = delete 语法,显式删除默认拷贝构造和赋值运算符。
class CopyBan {
public:
    CopyBan(const CopyBan&) = delete;
    CopyBan& operator=(const CopyBan&) = delete;
};

二、设计一个只能在堆上创建对象的类

1. 方法一:析构函数私有,提供destory接口释放资源

class HeapOnly
{
public:
	void Destroy()
	{
		delete this;
	}
private:
	~HeapOnly()
	{
		//...
	}
};

int main()
{
	//HeapOnly hp1;
	//static HeapOnly hp2;
	HeapOnly* hp3 = new HeapOnly;
	//delete hp3;
	hp3->Destroy();

	return 0;
}

2. 方法二:构造函数私有

步骤:

  1. 构造函数私有
  2. 提供静态成员函数创建对象
  3. 禁拷贝与赋值,防止利用拷贝创建栈上的对象

具体代码和使用如下:

class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}
private:
	HeapOnly()
	{
		//...
	}

	HeapOnly(const HeapOnly& hp) = delete;
	HeapOnly& operator=(const HeapOnly& hp) = delete;
};

int main()
{
	//HeapOnly hp1;
	//static HeapOnly hp2;
	//HeapOnly* hp3 = new HeapOnly;
	HeapOnly* hp3 = HeapOnly::CreateObj();
	//HeapOnly copy(*hp3);

	return 0;
}

三、 设计一个只能在栈上创建对象的类

1. 实现方式

步骤:

  1. 类比只能在堆上创建对象的类,先将构造函数私有
  2. 提供静态成员函数构造,不同的是只能在堆上创建对象的类new来创造,这里传值返回
  3. 为了避免这种情况:StackOnly* copy = new StackOnly(s),这样的拷贝方式创建堆上的对象,但是我们又不能禁拷贝和赋值,因此需要禁掉operator new,这样来写void* operator new(size_t) = delete

具体代码如下:

class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		StackOnly st;
		return st;
	}
private:
	StackOnly()
	{
		//...
	}

	// 对一个类实现专属operator new
	void* operator new(size_t size) = delete;
};

int main()
{
	//StackOnly hp1;
	//static StackOnly hp2;
	//StackOnly* hp3 = new StackOnly;
	StackOnly hp3 = StackOnly::CreateObj();
	StackOnly copy(hp3);

	//  new  operator new + 构造
	// StackOnly* hp4 = new StackOnly(hp3);

	return 0;
}

四、设计一个不能被继承的类

1. C++98 实现方式

  • 方法:将构造函数 private,防止继承。
class NonInherit {
public:
    static NonInherit GetInstance() {
        return NonInherit();
    }

private:
    NonInherit() {}
};

2. C++11 实现方式

  • 方法:使用 final 关键字。
class A final {
    // 该类无法被继承
};

五、设计一个只能创建一个对象(单例模式)

1. 单例模式介绍

  • 保证系统中某个类只有一个实例
  • 提供全局访问点
  • 应用场景:如全局配置管理。

2. 饿汉模式(Eager Singleton)

  • 特点:程序启动时即创建实例。(main函数之前就创建)
  • 优点:线程安全。
  • 缺点:可能导致启动慢。

(1)饿汉模式的实现步骤

代码:

namespace hungry
{
	class Singleton
	{
	public:
		// 2、提供获取单例对象的接口函数, 返回静态成员变量
		static Singleton& GetInstance()
		{
			return _sinst; 
		}

		void func();

		void Add(const pair<string, string>& kv)
		{
			_dict[kv.first] = kv.second;
		}

		void Print()
		{
			for (auto& e : _dict)
			{
				cout << e.first << ":" << e.second << endl;
			}
			cout << endl;
		}

	private:
		// 1、构造函数私有,防止外部直接创建对象
		Singleton() {}

		// 3、防拷贝:删除拷贝构造和赋值运算符,避免创建多个实例
		Singleton(const Singleton& s) = delete;
		Singleton& operator=(const Singleton& s) = delete;

		map<string, string> _dict;

		// 4、静态成员变量,在程序启动时就初始化
		static Singleton _sinst;
	};

	// 5、在类外部定义静态实例,在main函数执行前已创建
	Singleton Singleton::_sinst;
}

(2)代码解析

(1)构造函数私有化

Singleton() {}
  • 目的是防止外部代码通过 new 关键字创建对象,确保 Singleton 类只能在 GetInstance() 方法中创建实例。

(2)提供静态方法 GetInstance()

static Singleton& GetInstance()
{
    return _sinst;
}
  • 通过静态方法返回单例对象的引用,确保所有地方访问的都是同一个实例。

(3)防拷贝

Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
  • 防止拷贝和赋值,避免创建多个 Singleton 实例。

(4)静态成员变量 _sinst

static Singleton _sinst;
  • 程序启动时(main() 执行前)就创建该实例,无论是否真的需要。

(3)饿汉模式的优缺点

✅ 优点

  1. 线程安全

    • 静态成员 _sinst 在编译时创建,天然是线程安全的,无需额外同步措施(如 mutex)。
  2. 实现简单

    • 不需要加锁,避免了懒汉模式的 double-check 加锁复杂性。
  3. 访问速度快

    • 由于实例在程序启动时就已经创建,访问时无延迟,直接返回。

❌ 缺点

  1. 浪费资源

    • 如果该单例对象初始化内容很多,而程序运行期间根本没用到,就会浪费资源,降低程序启动速度。
  2. 难以控制对象创建顺序

    • 如果多个单例对象存在依赖关系(如 A 依赖 B),可能会导致未定义行为
    • 例如:
      class A {
          static A a_instance;
          B b; // A 依赖 B
      };
      
      class B {
          static B b_instance;
          A a; // B 依赖 A
      };
      
      • 由于 _sinst 在编译期静态初始化,两个类的创建顺序是由编译器决定的,可能会出现 A 还未初始化,但 B 已经尝试访问 A 的问题。

3. 懒汉模式(Lazy Singleton)

  • 特点:第一次使用时创建实例。
  • 优点:启动快,资源按需分配。
  • 缺点:线程不安全,需要加锁。

懒汉模式(Lazy Singleton)的实现思路解析
懒汉模式是一种 单例模式(Singleton Pattern)的实现方式,其特点是 延迟创建实例,即第一次使用时才创建对象,而不是程序启动时就初始化(像饿汉模式那样)。


(1)为什么使用懒汉模式?

懒汉模式的主要优点是延迟加载,适用于 对象创建成本较高、但并不是一定会用到的情况,比如:

  • 数据库连接
  • 日志管理
  • 需要动态管理生命周期的单例(如缓存数据)

(2)代码解析

完整代码:

namespace lazy
{
	class Singleton
	{
	public:
		// 2、提供获取单例对象的接口函数
		static Singleton& GetInstance()
		{
			if (_psinst == nullptr)
			{
				// 第一次调用 GetInstance 时创建单例对象
				_psinst = new Singleton;
			}

			return *_psinst;
		}

		// 释放单例对象(用于手动释放或持久化数据)
		static void DelInstance()
		{
			if (_psinst)
			{
				delete _psinst;
				_psinst = nullptr;
			}
		}

		void Add(const pair<string, string>& kv)
		{
			_dict[kv.first] = kv.second;
		}

		void Print()
		{
			for (auto& e : _dict)
			{
				cout << e.first << ":" << e.second << endl;
			}
			cout << endl;
		}

		// 3、GC(垃圾回收)类,在程序结束时自动释放 Singleton
		class GC
		{
		public:
			~GC()
			{
				lazy::Singleton::DelInstance();
			}
		};

	private:
		// 1、构造函数私有,防止外部直接创建对象
		Singleton()
		{
			// ...
		}

		~Singleton()
		{
			cout << "~Singleton()" << endl;

			// map数据写到文件中(持久化)
			FILE* fin = fopen("map.txt", "w");
			for (auto& e : _dict)
			{
				fputs(e.first.c_str(), fin);
				fputs(":", fin);
				fputs(e.second.c_str(), fin);
				fputs("\n", fin);
			}
			fclose(fin);
		}

		// 4、防拷贝
		Singleton(const Singleton& s) = delete;
		Singleton& operator=(const Singleton& s) = delete;

		map<string, string> _dict;
		static Singleton* _psinst; // 指向单例对象的指针
		static GC _gc; // 静态 GC 对象,自动释放 Singleton
	};

	// 5、静态成员变量初始化
	Singleton* Singleton::_psinst = nullptr; // 初始化单例指针为空
	Singleton::GC Singleton::_gc; // 在程序退出时自动释放 Singleton
}

int main()
{
	cout << &lazy::Singleton::GetInstance() << endl;
	cout << &lazy::Singleton::GetInstance() << endl;
	cout << &lazy::Singleton::GetInstance() << endl;

	// 添加数据
	lazy::Singleton::GetInstance().Add({ "xxx", "111" });
	lazy::Singleton::GetInstance().Add({ "yyy", "222" });
	lazy::Singleton::GetInstance().Add({ "zzz", "333" });
	lazy::Singleton::GetInstance().Add({ "abc", "333" });

	// 打印数据
	lazy::Singleton::GetInstance().Print();

	// 修改数据
	lazy::Singleton::GetInstance().Add({ "abc", "444" });
	lazy::Singleton::GetInstance().Print();

	// 不手动调用 DelInstance,程序结束时 GC 自动释放
	return 0;
}

(1)懒加载(Lazy Initialization)

static Singleton& GetInstance()
{
	if (_psinst == nullptr)
	{
		_psinst = new Singleton;
	}
	return *_psinst;
}

特点:

  • _psinst 指针初始化为 nullptr,意味着程序启动时不会创建实例。
  • 首次调用 GetInstance() 时才创建 Singleton 实例
  • 之后每次调用 GetInstance(),返回的都是同一个对象

(2)手动释放单例
一般来说,单例是不需要去释放的,

特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)(GC)

static void DelInstance()
{
	if (_psinst)
	{
		delete _psinst;
		_psinst = nullptr;
	}
}

作用:

  • 由于 Singleton 对象是动态创建的,所以需要手动释放。
  • DelInstance() 用于手动释放对象,当程序需要手动控制资源释放时可以调用。

(3)GC 机制(自动释放单例)

class GC
{
public:
	~GC()
	{
		lazy::Singleton::DelInstance();
	}
};

工作原理:

  • GCSingleton 内部的一个嵌套类。
  • static GC _gc; 是一个 静态成员变量,它的 析构函数会在程序退出时被调用,从而自动释放 Singleton 实例。

为什么要加 GC

  • 避免 内存泄漏,因为 Singleton 对象是 new 出来的,程序退出时如果不手动 delete,就会发生泄漏。
  • 确保 Singletonmain() 结束时释放,不会影响其他对象析构的顺序。

(4)防止拷贝

Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
  • 防止 拷贝构造赋值运算符,保证单例模式不被破坏。

(5)对象持久化

~Singleton()
{
	cout << "~Singleton()" << endl;

	// map数据写到文件中
	FILE* fin = fopen("map.txt", "w");
	for (auto& e : _dict)
	{
		fputs(e.first.c_str(), fin);
		fputs(":", fin);
		fputs(e.second.c_str(), fin);
		fputs("\n", fin);
	}
	fclose(fin);
}
  • Singleton 的析构函数中,把 _dict 数据写入文件,确保程序退出时数据不会丢失。

在这里插入图片描述

在这里插入图片描述


4. 饿汉VS懒汉

方式 线程安全 访问速度 资源消耗 适用场景
饿汉模式 ✅ 安全 ✅ 快 ❌ 可能浪费 频繁使用的单例对象(如日志、配置管理)
懒汉模式 ❌ 需加锁 ❌ 访问有延迟 ✅ 只在需要时创建 大量占用资源但不一定用到的单例

总结

到这里就结束啦~
谢谢大家,希望对您有所帮助~