C++特殊类的设计

1.不能被拷贝的类

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可

1.1 C++98的实现方式

将拷贝构造和赋值运算符重载设置为私有:

cpp 复制代码
#include <iostream>
using namespace std;

class Copyban {
public:
    Copyban(int a)
        :_a(a)
    {}
private:
    Copyban(const Copyban& d);
    Copyban& operator=(const Copyban& d);
private:
    int _a;
};
int main()
{
    Copyban c1(0);
    //Copyban c2 = c1;//error
    //Copyban c3(c1);//error
    return 0;
}

1.2 C++11实现方式

C++11扩展delete的用法,delete除了释放new申请的资源 外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数

cpp 复制代码
#include <iostream>
using namespace std;

class Copyban {
public:
    Copyban(int a)
        :_a(a)
    {}
    
private:
    Copyban(const Copyban& d) = delete;
    Copyban& operator=(const Copyban& d) = delete;
public:
    int _a;
};
int main()
{
    Copyban c1(1);
    //Copyban c2 = c1;//error
    //Copyban c3(c1);//error
    
    return 0;
}

2.只能在堆上创建对象

2.1 方式1

因栈对象销毁需调用析构函数,所以将析构函数设为私有(达到禁止在栈上创建对象的目标),并提供自定义 Destroy 成员函数供堆对象释放。

cpp 复制代码
#include <iostream>
using namespace std;

class HeapOnly {
public:
    HeapOnly(int a)
        :_a(a)
    {}
    void Destroy() {
        delete this;
    }
private:
    ~HeapOnly() {}
private:
    int _a;
};

int main() {
    HeapOnly* h1 = new HeapOnly(2);
    h1->Destroy();
    //HeapOnly h2;//error:不存在析构函数
    
    return 0;
}

2.2 方式2

将类的构造函数私有,拷贝构造声明成私有,防止别人调用拷贝在栈上生成对象。提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。

cpp 复制代码
#include <iostream>
using namespace std;

class HeapOnly {
public:
    // 静态成员函数:作为创建堆上对象的唯一入口
    static HeapOnly* createObj(int a) {
        return new HeapOnly(a);
    }
private:
    // 将构造函数设为私有:禁止类外部直接创建对象
    // 确保只能通过类提供的接口(CreateObj)创建对象
    HeapOnly(int a)
    :_a(a)
    {}

    // 禁用operator new:阻止通过new在栈上创建对象
    HeapOnly(const HeapOnly& h) = delete;
    HeapOnly& operator=(const HeapOnly& h) = delete;
private:
    int _a;
};

int main() {
    HeapOnly* h1 = HeapOnly::createObj(2);
    HeapOnly* h0(h1);
    HeapOnly* h4 = h0;

    //无法直接调用私有构造函数 HeapOnly(int a) 来创建对象
    HeapOnly* h2 = new HeapOnly(a);//error
    HeapOnly h3(2);//error
    
    return 0;
}

3.只能在栈上创建的类

将构造函数设为私有,仅允许通过静态成员函数在栈上创建对象,并禁用 operator new 以阻止堆上创建,同时允许拷贝构造以支持栈上对象的复制。

cpp 复制代码
#include <iostream>
using namespace std;

class StackOnly {
public:
    static StackOnly creatObj(int a) {
        StackOnly s(a);
        return s;
    }
private:
    // 将构造函数设为私有:禁止类外部直接创建对象
    // 确保只能通过类提供的接口(CreateObj)创建对象
    StackOnly(int a)
        :_a(a)
    {}
    // 禁用operator new:阻止通过new在堆上创建对象
    // 当尝试使用new StackOnly时,编译器会因该函数被删除而报错
    void* operator new(size_t size) = delete;
private:
    int _a;
};

int main() {
    StackOnly s = StackOnly::creatObj(2);
    StackOnly s2 = s2;
    StackOnly s3(s2);
    
    StackOnly* s1 = new StackOnly(2);//error
    return 0;
}

4.不能被继承的类

4.1 C++98的实现方式

构造函数私有化,派生类中调用不到基类的构造函数,则无法继承。

cpp 复制代码
#include <iostream>
using namespace std;


class NonInherit {
public:
    // 静态方法:获取类的实例(唯一创建方式)
    static NonInherit GetInstance(int a) {
        return NonInherit(a);
    }
private:
    // 构造函数私有化:禁止外部直接创建对象,同时阻止派生类调用
    NonInherit(int a)
        :_a(a)
    {}
private:
    int _a;
};

// 尝试继承NonInherit(编译报错)
/*
class Derived :public NonInherit {
public:
    Derived():NonInherit(0)// 派生类构造函数需要调用基类构造函数,但基类构造函数私有
    {} 
};
*/

int main() {
    NonInherit n = NonInherit::GetInstance(2);

    // 错误用法1:直接创建对象(构造函数私有)
    NonInherit n1(3);

    // 错误用法2:通过new创建对象(构造函数私有,无法调用)
    NonInherit* n2 = new NonInherit(6);
    return 0;
}

4.2 C++11的实现方式

final关键字,final修饰类,表示该类不能被继承

cpp 复制代码
#include <iostream>
using namespace std;

class NonInherit final
{
public:
    NonInherit(int a)
        :_a(a)
    {
    }
private:
    int _a;
};

// 尝试继承NonInherit(编译报错)
/*
class Derived :public NonInherit {
public:
    Derived():NonInherit(0)
    {} 
};
*/


int main() {
    NonInherit n(2);
    NonInherit* n2 = new NonInherit(6);
    return 0;
}

5.单例模式

5.1 设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。

5.2 单例模式的定义与使用场景

5.2.1 定义

一个类只能创建一个对象,即单例模式。

5.2.2 使用场景

**该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。**比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

5.3 实现模式

5.3.1 饿汉模式

定义: 不管用户将来是否使用,程序启动时(main函数之前) 就创建一个唯一的实例对象。

**优点:**实现容易。

缺点: 这些问题源于其对象创建时机固定在程序启动阶段,缺乏灵活性

  1. 单例对象初始化内容过多会影响启动速度。
  2. 当两个单例类存在依赖关系(如 B 依赖 A 且需 A 先创建)时难以处理。

**实现:**用饿汉模式实现了一个存储键值对字典的单例类,确保程序中只有唯一实例且在 main 函数前初始化。

思路:

  1. 提前创建实例:借助静态成员特性,在程序启动阶段(main 函数执行前)完成唯一实例的初始化。
  2. 限制创建途径:将构造逻辑设为私有,阻止外部直接创建对象,仅允许通过类提供的接口获取实例。
  3. 提供全局访问:通过静态方法作为获取实例的唯一入口,确保任何位置都能访问到同一个实例。
  4. 防止拷贝破坏:禁用拷贝构造和赋值操作,避免通过拷贝或赋值创建新实例,保证唯一性。
  1. 核心逻辑:利用静态成员初始化时机提前创建实例,结合私有构造与防拷贝机制阻断其他创建方式,实现程序运行期间类的实例全局唯一且可全局访问。
cpp 复制代码
#include <iostream>
#include <map>
using namespace std;

namespace hungry {
    class Singleton {
    public:
        //提供获取单例对象的接口函数(静态方法,返回唯一实例的引用)
		//保证全局只有一个对象,通过该接口访问
        static Singleton& getInstance() {
            return _sinst;
        }

        //测试用:自定义成员函数
        void fun();

        //向内部字典添加键值对
        void add(const pair<string, string>& kv) {
            _dict[kv.first] = kv.second;
        }

        //打印字典内部内容
        void print() {
            for (auto& kv : _dict) {
                cout << kv.first << " " << kv.second << endl;
            }
        }

    private:
        //构造函数私有化:禁止外部直接创建对象,确保只能通过GetInstance()获取实例
        Singleton() {
            cout << "Singleton构造函数调用(饿汉模式:main之前初始化)" << endl;
        }

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

    private:
        //单例对象内部数据:存储键值对的字典
        map<string, string> _dict;

        //静态成员变量:存储唯一的单例对象
        static Singleton _sinst;
    };

    Singleton Singleton::_sinst;

    void Singleton::fun() {
        _dict["default"] = "饿汉模式单例测试";
    }
}

int main() {
    cout << "进入main函数" << endl;
    // 获取单例对象(两次获取的是同一个实例)
    hungry::Singleton& s1 = hungry::Singleton::getInstance();
    hungry::Singleton& s2 = hungry::Singleton::getInstance();

    // 验证唯一性:打印地址,判断是否相同
    cout << "s1对的地址:" << &s1 << "   s2的地址:" << &s2 << endl;

    // 测试成员函数:添加数据并打印
    s1.add({ "hungry","饥饿的" });
    s2.add({ "lexical","词汇的" });
    s1.fun();
    cout << "\n单例内部是数据:" << endl;
    s2.print();

    // 尝试拷贝(编译报错,验证防拷贝机制)
    //hungry::Singleton s3(s1);//error
    //hungry::Singleton s4 = s2;//error

    return 0;
}

5.3.2 懒汉模式

定义: 核心是延迟初始化,仅在程序首次需要使用实例时才创建唯一的类实例,而非在程序启动阶段提前创建。

优点:

  1. 资源高效利用:避免初始化未使用的实例,减少程序启动时的资源消耗(如内存、计算资源),尤其适合实例创建成本高的场景。
  2. 按需加载:符合 "用的时候再创建" 的原则,降低对程序启动速度的影响,适合对启动性能敏感的场景。

缺点:

  1. 线程安全风险:多线程环境下,若多个线程同时首次请求实例,可能并发创建多个实例,破坏唯一性(需额外同步机制解决,增加实现复杂度)。
  2. 析构逻辑复杂:实例通常通过动态分配创建,需手动处理销毁(如通过辅助类自动触发或显式调用销毁接口),否则可能导致资源泄漏,相比自动析构的实现更繁琐。
  3. 首次访问开销:首次获取实例时需执行创建逻辑,可能比提前初始化的方式多一层判断和初始化操作(通常影响较小,但极端场景下需考虑)。

实现:

  1. 延迟初始化:仅在首次需要时才创建实例,而非程序启动阶段,实现按需加载。
  2. 控制创建入口:通过私有构造逻辑限制外部直接创建对象,仅允许通过特定接口获取实例。
  3. 保证唯一性:禁用拷贝和赋值操作,避免通过复制创建新实例,维持全局唯一。
  4. 自动释放机制:借助静态辅助对象的析构特性,在程序结束时自动触发实例销毁,简化资源管理。
cpp 复制代码
#include <iostream>
#include <map>
using namespace std;

namespace lazy {
    class Singleton {
    public:
        // 提供获取单例对象的接口函数
        // 懒汉模式核心:首次调用时才创建实例
        static Singleton& getInstance() {
            if (_psinst == nullptr) {
                _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& kv : _dict) {
                cout << kv.first << " " << kv.second << endl;
            }
        }

        //内部GC类:用于自动释放单例模式
        class GC {
        public:
            ~GC()//GC对象析构时,自动调用释放单例
            {
                lazy::Singleton::delInstance();
            }
        };
    private:
        // 构造函数私有:禁止外部直接创建对象
        Singleton(){}

        // 析构函数私有:仅能通过DelInstance()调用
        ~Singleton(){}

        // 防拷贝:禁用拷贝构造和赋值运算符
        Singleton(const Singleton& s) = delete;
        Singleton& operator=(const Singleton& s) = delete;
    private:
        // 单例内部的数据
        map<string, string> _dict;

        // 静态指针:指向唯一的单例对象
        static Singleton* _psinst;

        //静态GC对象:程序结束时自动析构
        static GC _gc;
    };

    Singleton* Singleton::_psinst = nullptr;//初始化静态成员
    Singleton::GC Singleton::_gc;//初始化静态GC对象
}

int main() {
    // 多次获取单例,验证唯一性(地址相同)
    cout << &lazy::Singleton::getInstance() << endl;
    cout << &lazy::Singleton::getInstance() << endl;

    // 向单例添加数据并打印
    lazy::Singleton::getInstance().add({ "lazy", "懒惰的" });
    lazy::Singleton::getInstance().add({ "single", "单身的" });
    lazy::Singleton::getInstance().print();
    return 0;
}
相关推荐
流星白龙4 小时前
【Qt】3.认识 Qt Creator 界面
java·开发语言·qt
Vanranrr4 小时前
nullptr vs NULL:C/C++ 空指针的演变史
c语言·c++
机灵猫4 小时前
深入理解 Java 类加载与垃圾回收机制:从原理到实践
java·开发语言
weixin_307779134 小时前
AWS Redshift 数据仓库完整配置与自动化管理指南
开发语言·数据仓库·python·云计算·aws
切糕师学AI4 小时前
【多线程】阻塞等待(Blocking Wait)(以C++为例)
c++·多线程·并发编程·阻塞等待
Sunsets_Red4 小时前
差分操作正确性证明
java·c语言·c++·python·算法·c#
伐尘5 小时前
【Qt】QTableWidget 自定义排序功能实现
开发语言·qt
第七序章5 小时前
【C++】AVL树的平衡机制与实现详解(附思维导图)
c语言·c++·人工智能·机器学习
ajassi20005 小时前
开源 C++ QT QML 开发(十九)多媒体--音频录制
c++·qt·开源