文章目录
前言?一、设计一个类,不能被拷贝?1.1 问题背景?1.2 解决方案?1.3 实现方式?1.4 实现原理 ?二、设计一个类,只能在堆上创建对象?2.1 问题背景?2.2 解决方案?2.3 实现方式?2.4 实现原理 ?三、设计一个类,只能在栈上创建对象?3.1 问题背景?3.2 解决方案?3.3 实现方式?3.4 实现原理 ?四、设计一个类,不能被继承?4.1 问题背景?4.2 解决方案?1. 实现方式 1:使用 `final`?2. 实现方式 2:析构函数 `final` ?4.3 实现原理?输出 ?五、设计一个类,只能创建一个对象(单例模式)?5.1 什么是单例模式??5.2 为什么使用单例模式??1. 节约资源?2. 状态一致性?3. 控制访问 ?5.3 单例模式的实现方式?1. 饿汉式单例代码实现实现细节优点缺点 ?2. 懒汉式单例实现细节优点缺点 ?3. 为什么用 `static`? 结语
前言
C++ 是一门充满无限可能的语言,凭借其丰富的特性和灵活的抽象能力,使得开发者能够实现许多令人惊叹的功能。特殊类的设计与实现是 C++ 编程中的一项重要实践,它不仅考验开发者对语言特性的理解,更体现了对程序架构的设计思考。在这篇文章中,我们将深入探讨 C++ 特殊类实现的核心思路,从设计理念到代码技巧,带你走进 C++ 世界的奇妙殿堂。
?一、设计一个类,不能被拷贝
?1.1 问题背景
在某些情况下,类的对象不应该被拷贝或赋值。例如,文件句柄或资源管理类需要独占资源,如果被拷贝会导致多个对象同时操作相同资源,可能引发未定义行为。
?1.2 解决方案
通过删除拷贝构造函数和拷贝赋值运算符,可以禁止类的拷贝。
?1.3 实现方式
#include <iostream>class NonCopyable {public: NonCopyable() { std::cout << "Constructor called" << std::endl; } ~NonCopyable() { std::cout << "Destructor called" << std::endl; } // 禁止拷贝构造 NonCopyable(const NonCopyable&) = delete; // 禁止拷贝赋值 NonCopyable& operator=(const NonCopyable&) = delete;};int main() { NonCopyable obj1; // 正常创建对象 // NonCopyable obj2 = obj1; // 编译错误:拷贝构造被禁用 // obj1 = obj2; // 编译错误:拷贝赋值被禁用 return 0;}
?1.4 实现原理
= delete
明确告诉编译器禁止使用该函数。如果尝试调用被删除的函数,会直接导致编译错误。 输出
Constructor calledDestructor called
?二、设计一个类,只能在堆上创建对象
?2.1 问题背景
有时需要确保对象只能通过动态分配方式(即堆上创建),例如某些需要精确控制生命周期的对象。
?2.2 解决方案
将析构函数声明为private
或 protected
,防止栈上的对象自动销毁。提供一个静态工厂方法,确保对象只能通过堆分配。 ?2.3 实现方式
#include <iostream>class HeapOnly {public: static HeapOnly* createInstance() { return new HeapOnly(); } void display() { std::cout << "Object created on heap" << std::endl; }private: HeapOnly() { std::cout << "Constructor called" << std::endl; } ~HeapOnly() { std::cout << "Destructor called" << std::endl; }};int main() { HeapOnly* obj = HeapOnly::createInstance(); obj->display(); delete obj; // 必须手动释放内存 return 0;}
?2.4 实现原理
析构函数是private
或 protected
的,因此栈上的对象无法销毁。通过静态工厂方法控制对象的创建,确保它只能在堆上分配。 输出
Constructor calledObject created on heapDestructor called
?三、设计一个类,只能在栈上创建对象
?3.1 问题背景
某些对象必须严格限定其生命周期,例如某些局部作用域的临时对象。如果它们被动态分配,可能会增加内存管理的复杂性。
?3.2 解决方案
通过删除 new
和 delete
运算符,禁止对象在堆上创建或销毁。
?3.3 实现方式
#include <iostream>class StackOnly {public: StackOnly() { std::cout << "Constructor called" << std::endl; } ~StackOnly() { std::cout << "Destructor called" << std::endl; }private: // 禁止使用 new 和 delete void* operator new(size_t) = delete; void operator delete(void*) = delete;};int main() { StackOnly obj; // 正常:对象在栈上创建 // StackOnly* obj = new StackOnly(); // 编译错误 return 0;}
?3.4 实现原理
删除operator new
和 operator delete
,防止堆上的内存分配和释放。 输出
Constructor calledDestructor called
?四、设计一个类,不能被继承
?4.1 问题背景
在某些情况下,类的设计目的是提供独立功能,不需要被扩展。例如,某些工具类或底层实现类需要避免被继承以保护其行为。
?4.2 解决方案
使用final
关键字,直接禁止继承。将析构函数声明为 final
,间接禁止继承。 ?1. 实现方式 1:使用 final
#include <iostream>class NonInheritable final {public: NonInheritable() { std::cout << "Constructor called" << std::endl; }};// class Derived : public NonInheritable {}; // 编译错误int main() { NonInheritable obj; return 0;}
?2. 实现方式 2:析构函数 final
#include <iostream>class NonInheritable {public: NonInheritable() { std::cout << "Constructor called" << std::endl; } virtual ~NonInheritable() final { std::cout << "Destructor called" << std::endl; }};// class Derived : public NonInheritable {}; // 编译错误int main() { NonInheritable obj; return 0;}
?4.3 实现原理
final
关键字用于类或虚函数,表示它不能被继承或重写。 ?输出
Constructor calledDestructor called
?五、设计一个类,只能创建一个对象(单例模式)
?5.1 什么是单例模式?
单例模式(Singleton Pattern)是一种常用的设计模式,其目的是确保一个类在整个程序中只能创建一个实例,并提供全局访问该实例的途径。
单例模式的主要特点:
唯一性:整个程序运行期间,单例类只能创建一个实例。全局访问:可以通过一个公共接口访问该实例,而无需显式管理对象的生命周期。?5.2 为什么使用单例模式?
?1. 节约资源
某些对象的创建和销毁代价较高,例如数据库连接池、线程池、日志系统等,使用单例模式可以复用同一个实例。?2. 状态一致性
单例模式提供一个全局唯一的实例,可以确保状态在不同调用之间保持一致。例如,配置管理器类可以通过单例模式提供全局统一的配置信息。?3. 控制访问
单例模式可以限制实例的创建,并集中控制资源的初始化和访问。例如,限制某些硬件设备或文件的访问,确保只有一个实例对资源进行操作。?5.3 单例模式的实现方式
单例模式的实现有两种主要方式:懒汉式和饿汉式。
?1. 饿汉式单例
饿汉式单例在程序启动时就创建实例,确保线程安全,但可能浪费资源。
代码实现
#include <iostream>class Singleton {public: static Singleton* getInstance() { return &instance; } void show() const { std::cout << "Singleton Instance Address: " << this << std::endl; }private: Singleton() = default; // 私有构造函数 ~Singleton() = default; Singleton(const Singleton&) = delete; // 禁止拷贝构造 Singleton& operator=(const Singleton&) = delete; // 禁止拷贝赋值 static Singleton instance; // 静态实例};// 静态实例定义Singleton Singleton::instance;int main() { Singleton* s1 = Singleton::getInstance(); Singleton* s2 = Singleton::getInstance(); s1->show(); s2->show(); return 0;}
输出
Singleton Instance Address: 00007FF72021F470Singleton Instance Address: 00007FF72021F470
实现细节
线程安全:静态成员在程序启动时初始化,因此天然线程安全。资源预先分配:程序启动时立即创建实例,无论是否会使用。优点
简单且线程安全。无需额外的同步机制。缺点
程序启动时就分配资源,可能导致资源浪费。?2. 懒汉式单例
懒汉式单例在第一次需要时才创建实例(懒加载),避免了程序启动时就创建对象的额外开销。
代码实现
#include <iostream>#include <mutex>class Singleton {public: // 获取实例 static Singleton* getInstance() { static std::once_flag flag; // 用于线程安全的初始化 std::call_once(flag, []()) { instance = new Singleton(); }); return instance; } void show() const { std::cout << "Singleton Instance Address: " << this << std::endl; }private: Singleton() = default; // 私有构造函数 ~Singleton() = default; Singleton(const Singleton&) = delete; // 禁止拷贝构造 Singleton& operator=(const Singleton&) = delete; // 禁止拷贝赋值 static Singleton* instance; // 静态指针,存储唯一实例};// 初始化静态成员Singleton* Singleton::instance = nullptr;int main() { Singleton* s1 = Singleton::getInstance(); Singleton* s2 = Singleton::getInstance(); s1->show(); s2->show(); return 0;}
输出
Singleton Instance Address: 000001C9AABB7E70Singleton Instance Address: 000001C9AABB7E70
实现细节
线程安全:使用std::call_once
确保实例的初始化是线程安全的。懒加载:实例在第一次调用 getInstance
时才被创建。 优点
避免程序启动时就分配内存(节省资源)。延迟创建,使用时才分配资源。缺点
在高并发场景下,需要确保线程安全。?3. 为什么用 static
?
a. 方法或变量共享
静态成员和方法属于类,而不是某个对象。这意味着类的所有实例都共享相同的静态成员,而不是为每个实例单独分配。b. 不需要实例化即可访问
静态方法或变量可以通过类名::静态成员
直接访问,不需要创建类的实例。适用于一些不依赖对象的全局逻辑或工具方法。 c. 生命周期与程序一致
静态成员的生命周期从程序启动到结束,适合需要在整个程序期间存在的资源。结语
特殊类的实现是编程语言能力与设计思想的交汇点,也是开发者能力的试金石。通过对 C++ 特殊类的深刻理解与实践,不仅可以提升代码的复用性与健壮性,更能培养对复杂系统的抽象能力与洞察力。希望这篇文章能为你提供新的思路与灵感,让 C++ 成为你构建优雅代码与解决复杂问题的强大工具!
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!