当前位置:首页 » 《关注互联网》 » 正文

代码背后的哲思:C++特殊类实现的艺术与科学

24 人参与  2024年12月03日 16:01  分类 : 《关注互联网》  评论

点击全文阅读


在这里插入图片描述

文章目录

前言?一、设计一个类,不能被拷贝?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 解决方案

将析构函数声明为 privateprotected,防止栈上的对象自动销毁。提供一个静态工厂方法,确保对象只能通过堆分配。

?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 实现原理

析构函数是 privateprotected 的,因此栈上的对象无法销毁。通过静态工厂方法控制对象的创建,确保它只能在堆上分配。

输出

Constructor calledObject created on heapDestructor called

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

?3.1 问题背景

某些对象必须严格限定其生命周期,例如某些局部作用域的临时对象。如果它们被动态分配,可能会增加内存管理的复杂性。

?3.2 解决方案

通过删除 newdelete 运算符,禁止对象在堆上创建或销毁。

?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 newoperator 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前进的动力!

在这里插入图片描述


点击全文阅读


本文链接:http://zhangshiyu.com/post/196027.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1