• C++特殊类设计


    目录

    1. 请设计一个不能被拷贝的类

    2. 请设计一个只能在堆上创建对象的类

    2.1. 第一种方式:将析构函数设为私有

    2.2. 第二种方式:将构造函数设为私有

    3. 请设计一个只可以在栈上创建对象的类

    4. 请设计一个不可被继承的类

    5. 设计模式

    5.1. 单例模式

    5.1.1. 饿汉模式

    5.1.2. 懒汉模式

    5.1.3. 单例对象的释放问题


    1. 请设计一个不能被拷贝的类

    1. namespace Xq
    2. {
    3. class reject_copy
    4. {
    5. public:
    6. //C++ 11:
    7. //利用delete: 表示让编译器删除掉该默认成员函数
    8. reject_copy(const reject_copy& copy) = delete;
    9. reject_copy& operator=(const reject_copy& copy) = delete;
    10. private:
    11. //C++ 98:
    12. // 将copy constructor 和 operator= 只声明不实现,并设为私有
    13. // 设为私有的原因:防止有人在类外定义
    14. // 只声明不定义:因为该函数根本就不会被调用,不写反而简单
    15. // 而且不定义可以防止成员函数内部拷贝
    16. reject_copy(const reject_copy& copy);
    17. reject_copy& operator=(const reject_copy& copy);
    18. };
    19. }

    2. 请设计一个只能在堆上创建对象的类

    2.1. 第一种方式:将析构函数设为私有

    当一个类的析构函数被设置为私有,那么此时无法在栈上和静态区创建对象。

    因为正常情况下,栈上和静态区的对象都需要调用析构函数。

    而此时new可以创建对象,但是由于delete释放资源时会调用析构函数,而析构函数是私有的,因此会报错。

    但我们此时可以利用类的成员函数释放资源。

    destroy_one:将该函数设计为类的成员函数,通过先是传参,调用析构函数

    destroy_two:  同上者一样,但是我们可以直接释放this指针,在类内调用析构函数

    destroy_three: 将该函数设置为静态的成员函数,通过显式传参,调用析构函数

    1. namespace Xq
    2. {
    3. // 第一种设计方案:将析构函数私有
    4. class heap_only
    5. {
    6. public:
    7. // 释放资源的第一种方式
    8. void destroy_one(heap_only* hp)
    9. {
    10. delete hp;
    11. }
    12. // 释放资源的第二种方式
    13. void destroy_two()
    14. {
    15. delete this;
    16. }
    17. // 释放资源的第三种方式
    18. static void destroy_three(heap_only* hp)
    19. {
    20. delete hp;
    21. }
    22. private:
    23. ~heap_only()
    24. {
    25. std::cout << "~heap_only()" << std::endl;
    26. }
    27. };
    28. }
    29. void Test2(void)
    30. {
    31. // 设计只能在堆上创建对象的类,在这里 将析构函数设为私有
    32. //Xq::heap_only stack_hp; // 由于析构私有,无法再栈上创建对象
    33. //static Xq::heap_only static_hp; // 由于析构私有,无法在静态区创建对象
    34. // 虽然可以new对象,但无法delete(析构私有)
    35. Xq::heap_only* heap_hp = new Xq::heap_only;
    36. // 因此我们可以提供一个用于释放资源的函数
    37. //heap_hp->destroy_one(heap_hp);
    38. //heap_hp->destroy_two();
    39. Xq::heap_only::destroy_three(heap_hp);
    40. }

    2.2. 第二种方式:将构造函数设为私有

    将构造函数设为私有会导致 栈、静态区、堆都无法构造对象

    因此我们可以写一个成员函数creat_target,但是调用成员函数需要一个对象,因此我们需要将creat_target设置为静态的成员函数。

    其creat_target函数内部控制对象是被new出来的

    但是此时我们还需要防止拷贝构造。

    1. namespace Xq
    2. {
    3. // 将构造函数私有
    4. class heap_only
    5. {
    6. public:
    7. // 提供一个公有的静态的获取对象的方式
    8. // 该对象被控制为new出来的
    9. static heap_only* creat_target()
    10. {
    11. heap_only* ret_ptr = new heap_only;
    12. return ret_ptr;
    13. }
    14. ~heap_only()
    15. {
    16. std::cout << "~heap_only()" << std::endl;
    17. }
    18. // C++98的做法,只声明不实现.并设置为私有
    19. /*private:
    20. heap_only(const heap_only& copy);
    21. heap_only& operator=(const heap_only& copy);*/
    22. // C++11的做法 delete
    23. heap_only(const heap_only& copy) = delete;
    24. heap_only& operator=(const heap_only& copy) = delete;
    25. private:
    26. heap_only(){}
    27. };
    28. }
    29. void Test3(void)
    30. {
    31. //Xq::heap_only stack_hp; // 由于构造私有,无法再栈上创建对象
    32. //static Xq::heap_only static_hp; // 由于构造私有,无法在静态区创建对象
    33. //Xq::heap_only* heap_hp = new heap_only; //由于构造私有,无法new对象
    34. Xq::heap_only* heap_hp = Xq::heap_only::creat_target();
    35. Xq::heap_only stack_hp(*heap_hp); // 在这里报错,因为我们将拷贝构造设置为delete
    36. delete heap_hp;
    37. }

    3. 请设计一个只可以在栈上创建对象的类

    1. namespace Xq
    2. {
    3. // 将构造函数私有
    4. class stack_only
    5. {
    6. public:
    7. static stack_only creat_target()
    8. {
    9. stack_only st;
    10. return st;
    11. }
    12. // 在这里不能将拷贝构造限制死,因为上面的静态成员函数需要拷贝构造
    13. /*stack_only(const stack_only& copy) = delete;
    14. stack_only& operator=(const stack_only& copy) = delete;*/
    15. // 但我们可以限制一下new的拷贝构造
    16. void* operator new(size_t n) = delete;
    17. ~stack_only()
    18. {
    19. std::cout << "~stack_only()" << std::endl;
    20. }
    21. private:
    22. stack_only()
    23. {
    24. std::cout << "stack_only()" << std::endl;
    25. }
    26. };
    27. }
    28. void Test4(void)
    29. {
    30. //Xq::stack_only st;
    31. /*static Xq::stack_only static_st;
    32. Xq::stack_only* heap_st = new Xq::stack_only;*/
    33. // 将构造函数私有会使 栈上、静态区、堆 都无法构建对象
    34. // 但我们可以显示实现一个静态的公有成员函数,利用这个函数构造对象,其内部实现控制为栈上对象
    35. Xq::stack_only st = Xq::stack_only::creat_target();
    36. // 但是带来的问题就是,此时可以调拷贝构造
    37. static Xq::stack_only static_st(st); // 在这里不好限制,算是一个缺陷
    38. Xq::stack_only* heap_st = new Xq::stack_only(st); // 被限制了,编译报错
    39. // 有人说,把拷贝构造限制死不就行了?
    40. // 但是如果限制死了,第一种方式也不可以构造对象了,因为这个静态函数其内部实现是通过局部变量传值返回的
    41. // 其传值返回就是拷贝构造
    42. // 因此我们在这里只可以限制在堆上的拷贝构造,将这个类的operator new 限制死
    43. }

    4. 请设计一个不可被继承的类

    1. namespace Xq
    2. {
    3. // C++98做法:将构造函数私有
    4. // 由于派生类调不到基类的构造函数,因此无法继承
    5. class reject_inherit
    6. {
    7. private:
    8. reject_inherit(){}
    9. };
    10. // C++11做法: final修饰的类不可被继承
    11. class reject_inherit final
    12. {
    13. public:
    14. reject_inherit(){}
    15. };
    16. }

    5. 设计模式

    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

    常见的设计模式有:

    C++语言是一种广泛应用于系统开发和高性能应用程序的编程语言,对于设计模式也有很好的支持。以下是一些常见的C++设计模式:

    1. 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。

    2. 工厂模式(Factory Pattern):通过工厂类创建对象,隐藏创建对象的具体细节,实现对象的解耦和扩展。

    3. 观察者模式(Observer Pattern):定义对象间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。

    4. 策略模式(Strategy Pattern):定义一族算法,将每个算法都封装起来,并使它们可以互换使用,使得算法的变化可以独立于使用算法的客户端。

    5. 装饰者模式(Decorator Pattern):动态地给一个对象添加一些额外的职责,同时又不改变其结构。

    6. 适配器模式(Adapter Pattern):将一个类的接口转换成客户端所期待的另一种接口,从而使原本不兼容的类能够一起工作。

    这些设计模式可以帮助开发人员提高代码的可读性、可维护性和可扩展性,并促进软件设计中的重用和模块化。请注意,设计模式的选择取决于具体应用场景和需求,在使用时需要谨慎权衡和灵活应用。

    5.1. 单例模式

    单例模式(Singleton Pattern)是一种创建型设计模式,用于确保一个类只有一个唯一的实例,并提供一个全局访问点来访问该实例,该实例被所有程序模块共享。

    在单例模式中,类的构造函数被定义为私有的,这样外部无法直接创建实例。而是通过类内部提供的静态方法或者属性来获取实例,该方法或属性会自动判断是否已经存在实例。如果已经存在实例,则返回该实例;如果不存在实例,则创建一个新的实例,并返回。

    单例模式适用于需要共享资源或提供唯一实例的场景,例如日志记录器、数据库连接池、线程池等。通过使用单例模式,可以确保全局只有一个实例存在,避免了资源的重复创建与浪费,并能够方便地在多个地方共享该实例。

    单例模式的实现方式主要有以下几种:

    1. 懒汉式(Lazy Initialization):在第一次使用时才创建实例。如果不存在实例,则在获取实例时创建。这种方式延迟了实例的创建,但在多线程环境下需要考虑线程安全性。

    2. 饿汉式(Eager Initialization):在类加载时就创建实例。保证了实例的唯一性和线程安全性,但可能会提前占用资源。

    3. 双重检查锁(Double-Checked Locking):结合了懒汉式和饿汉式的优点。在第一次获取实例时进行双重检查,避免了不必要的同步开销,提高了性能和线程安全性。

    4. 静态内部类(Static Inner Class):利用静态内部类的特性,在外部类加载时不会创建实例,只有实际调用内部类时才会触发实例的创建。这种方式既实现了延迟加载,又保证了线程安全性。

    5. 枚举类(Enum Class):使用枚举实现单例模式,枚举类型的实例是在类加载时完成初始化的,保证了线程安全性和实例唯一性。

    不同的实现方式适用于不同的场景,需根据具体的需求和环境选择合适的方式。需要注意的是,在多线程环境下,需要考虑线程安全性,避免出现竞态条件和实例的重复创建。

    在这里以饿汉和懒汉模式简单实现单例模式:

    5.1.1. 饿汉模式

    饿汉模式是在程序启动时就会创建单例对象,不管是否用到,始终保持实例的唯一性和全局可访问性。这种实现方式的特点,程序已启动就会创建该实例,并始终保持其存在,无论是否实际使用。

    饿汉模式的优点:在main()之前就已经完成了对象的实例化,因此线程安全,不需要考虑多线程情况下的同步问题

    缺点:当一个程序中,有多个单例,并且有先后创建初始化顺序要求时,饿汉无法控制;

    饿汉单例类,如果初始化时任务多,会影响程序启动速度。

    1. // 第一种实现方案
    2. namespace Xq
    3. {
    4. class single_case
    5. {
    6. public:
    7. // 该静态成员函数提供了获取唯一实例的方式
    8. static single_case& get_only_target()
    9. {
    10. return _only_target;
    11. }
    12. private:
    13. // 将构造函数私有化
    14. single_case(){}
    15. static single_case _only_target; // 注意这里只是声明
    16. };
    17. //在main()之前就完成单例对象的初始化
    18. single_case single_case::_only_target; // 需要在类外定义这个唯一实例
    19. }
    20. int main()
    21. {
    22. Xq::single_case::get_only_target(); // 获取唯一实例
    23. }
    24. // 第二种实现方案
    25. namespace Xq
    26. {
    27. class single_case
    28. {
    29. public:
    30. // 该静态的成员函数提供了获取唯一实例地址的方式
    31. static single_case* get_ptr_only_target()
    32. {
    33. return _ptr_only_target;
    34. }
    35. private:
    36. // 构造函数私有
    37. single_case(){}
    38. static single_case* _ptr_only_target; //唯一实例的地址,但这里只是声明
    39. };
    40. //在main()之前就完成单例对象的初始化
    41. single_case* single_case::_ptr_only_target = new single_case; // 这里才是定义
    42. }
    43. int main()
    44. {
    45. Xq::single_case::get_ptr_only_target(); // 获取唯一实例的地址
    46. }

    5.1.2. 懒汉模式

    懒汉模式(Lazy Initialization)是一种单例模式的实现方式。它在第一次使用时才创建实例,而不是在类加载阶段就创建。

    懒汉模式的特点是延迟加载,即只有在第一次使用时才会创建实例。当不存在实例时,会在获取实例时创建一个新的实例,并在后续的调用中返回这个实例。如果已经存在实例,则直接返回现有的实例。

    懒汉模式的典型特点是在获取实例的方法或属性中判断实例是否已经创建,如果没有创建,则进行实例的创建。

    懒汉模式的优点:可以节省资源,避免不必要的实例创建和消耗,不影响启动速度;一个程序中,在有多个单例的情况下,可以控制顺序。

    懒汉模式的缺点:需要考虑线程安全问题,相对饿汉模式较为复杂。

    1. namespace Xq
    2. {
    3. class single_case
    4. {
    5. public:
    6. static single_case* get_ptr_only_target()
    7. {
    8. // 当唯一实例第一次使用时,才会实例化出资源
    9. // 延迟加载
    10. if (_ptr_only_target == nullptr)
    11. {
    12. _ptr_only_target = new single_case;
    13. }
    14. // 如果已经实例化了,返回当前实例对象
    15. return _ptr_only_target;
    16. }
    17. // 实现一个内嵌垃圾回收类,回收实例对象new的资源
    18. class resources_recovery
    19. {
    20. public:
    21. ~resources_recovery()
    22. {
    23. if (_ptr_only_target)
    24. {
    25. delete _ptr_only_target;
    26. }
    27. }
    28. static resources_recovery _delete_ptr; // 注意在这里只是声明
    29. };
    30. void print()
    31. {
    32. std::cout << "heihei" << std::endl;
    33. }
    34. ~single_case()
    35. {
    36. std::cout << "~single_case()" << std::endl;
    37. }
    38. private:
    39. single_case(){}
    40. static single_case* _ptr_only_target; // 唯一实例的地址,但这里只是声明
    41. };
    42. single_case* single_case::_ptr_only_target = nullptr; // 懒汉模式,当第一次使用时才会实例化出资源
    43. // 定义一个静态成员变量,进程结束时,系统会自动调用它的析构函数从而释放单例对象
    44. single_case::resources_recovery single_case::resources_recovery::_delete_ptr;
    45. }

    5.1.3. 单例对象的释放问题

    1、一般情况下,单例对象不需要我们显示释放的(进程正常结束,自动释放该资源)。一般这个进程运行期间都可能会用这个对象。

    2、单例对象再进程正常结束后,也会资源释放。

    3、有些特殊场景需要释放,比如单例对象析构时,要进行一些持久化操作(例如:往磁盘文件、数据库写),我们可以实现一个用来回收实例对象的内部类。

  • 相关阅读:
    人脸检测几种模型在RK3399上推理速度对比
    Linux Systemd 配置开机自启
    电动机保护器的作用
    暖心的虚拟恋人尽在烟雨树洞
    当创建pvc后,kubernetes组件如何协作
    CentOS7启动进入紧急模式
    Flutter学习9 - http 中 get/post 请求示例
    教育行业的网络安全:保护学生数据与防范网络欺凌
    51单片机外设篇:点阵式LCD
    SAP SD模块前台操作
  • 原文地址:https://blog.csdn.net/m0_62229058/article/details/133936898