文章目录
- 一、设计一个不允许拷贝的类
- 二、设计一个只能在堆上实例对象的类
- 三、设计一个只能在栈上创建对象的类
- 四、设计一个不能被继承的类
- 五、设计一个只能创建一个对象的类(单例模式)
一、设计一个不允许拷贝的类
1、方法一:将拷贝构造和赋值重载声明不定义并且放到私有域。
解释:
- 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了。
- 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写 反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
2、方法二:直接将拷贝构造和赋值重载删除。
3、演示
//防拷贝 - 不允许拷贝
class Base1
{
public:Base1(int a = 10):_a(a){}private://c++98做法 -- 将函数声明不定义加不声明//Base1(const Base& b);//Base1& operator=(const Base& b);//c++11及以后做法 -- 将函数删除Base1(const Base1& b) = delete;Base1& operator=(const Base1& b) = delete;int _a = 0;
};void test01()
{Base1 b1;//会报错//Base1 b2(b1);
}
二、设计一个只能在堆上实例对象的类
1、方法:将构造函数私有化并且将拷贝构造和赋值重载删除(或者声明不定义到私有域中),再在类内部提供一个静态成员函数。
解释:
(1)将构造函数私有化:这样就只允许在类内实例类对象了。
(2)拷贝构造和赋值重载删除:在其他位置拷贝。
(3)静态成员函数:用于在堆上申请对象,并返回。
2、演示
class Base2
{
public://通过函数接口在堆上new一个对象出来static Base2& NewBase2(){Base2* b = new Base2;return *b;}private://将构造私有化Base2(int a = 10) :_a(a){};//反拷贝//c++98做法 -- 将函数声明不定义加不声明//Base2(const Base& b);//Base2& operator=(const Base& b);//c++11及以后做法 -- 将函数删除Base2(const Base2& b) = delete;Base2& operator=(const Base2& b) = delete;int _a = 0;
};void test02()
{//在栈上不行//Base2 b;//通过函数接口Base2 &b = Base2::NewBase2();//不能拷贝//Base2 b1(b);
}
三、设计一个只能在栈上创建对象的类
1、方法:将构造函数私有化并且将拷贝构造和赋值重载删除(或者声明不定义到私有域中),需要实现一个移动构造,再在类内部提供一个静态成员函数。
解释:
(1)将构造函数私有化:这样就只允许在类内实例类对象了。
(2)拷贝构造和赋值重载删除:在其他位置拷贝。
(3)静态成员函数:用于在栈上申请对象,并返回。
(4)实现移动构造:用于上述静态函数返回时使用。
2、演示
//只允许在栈上
class Base3
{
public:static Base3 StackBase3(){return Base3();}Base3(Base3&& b):_a(b._a){}
private://将构造私有化Base3(int a = 10) :_a(a){};//反拷贝//c++98做法 -- 将函数声明不定义//Base3(const Base& b);//Base3& operator=(const Base& b);//c++11及以后做法 -- 将函数删除Base3(const Base3& b) = delete;Base3& operator=(const Base3& b) = delete;int _a = 0;
};void test03()
{//不能new了//Base3* b = new Base3;//使用移动构造Base3 b = Base3::StackBase3();//防不了这种情况
// Base3* b1 = new Base3(move(b));
}
四、设计一个不能被继承的类
1、方法一:将基类构造函数私有,这样派生类就调用不了基类的构造函数,这样就无法继承了。
class Base
{private:Base() {}
};class Derived:public Base
{
public:Derived() :Base(){}}
2、方法二:final
关键字,final
修饰类,表示该类不能被继承
class Base final
{private:Base() {}
};class Derived:public Base
{
public:
};
五、设计一个只能创建一个对象的类(单例模式)
1、单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
2、方法一:饿汉模式
(1)概念:就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
(2)实现细节:
(1)在类内声明一个该类类型的静态成员变量,再到类外定义该静态成员变量。
(2)提供一个静态成员函数,返回该静态成员变量的引用。
(3)做防拷贝操作,并将该类构造函数私有化。
(3)优缺点
优点
(1)实现简单:饿汉模式在类加载时就完成了单例的初始化,这种方式简单直接,易于理解和实现。
(2)线程安全:由于单例对象在类加载时就已经创建,所以多线程环境下不会存在访问冲突,无需担心线程安全问题。
(3)执行效率高:在单例对象创建之后,所有对该单例的访问都直接返回已创建的对象,无需再进行创建或同步操作,因此在频繁访问的情况下具有较高的执行效率。
缺点
(1)资源浪费:无论是否需要使用单例对象,它都会在类加载时被创建,这可能导致在不需要使用单例对象时,系统资源被不必要地占用。特别是当单例对象占用资源较多或初始化时间较长时,这种资源浪费可能更为明显。
(2)灵活性不足:饿汉模式在类加载时就完成了单例的初始化,这意味着如果单例对象的创建依赖于某些外部条件或参数,那么这些条件或参数必须在类加载之前就已经确定或准备好,这限制了单例模式的灵活性。
(3)无法懒加载:与懒汉模式相比,饿汉模式无法实现单例对象的懒加载,即只有在真正需要时才创建单例对象。这可能会导致在某些情况下,程序启动时间较长或启动时需要加载较多的资源。
(4)演示
//饿汉模式
class Base4
{
public://返回唯一的实例的静态类static Base4& RetuntB(){return b;}//输出信息void Printf(){cout << s << endl;}private:Base4(){}Base4(const Base4& b) = delete;Base4& operator=(const Base4& b) = delete;private:string s = "hello"; //假设这是储存的信息static Base4 b;};Base4 Base4::b;void test04()
{Base4::RetuntB().Printf();}
3、方法二:懒汉模式
(1)概念:在使用时再去实例化对象。
(2)实现细节:
(1)在类内声明一个该类类型的指针,再到类外定义为
nullprt
。
(2)提供一个静态成员函数,返回该静态成员变量的引用(再使用时发现为空就向堆申请)。
(3)做防拷贝操作,并将该类构造函数私有化。
(4)通过实现一个静态的函数来清理这块空间,或者实现一个内部类,在内部类的析构函数进行清理。
(3)优缺点
优点
(1)资源利用率高:懒汉模式实现了单例的懒加载,即单例对象在第一次被使用时才创建,这可以确保在不需要使用单例对象时不会占用系统资源,从而提高了资源利用率。
(2)灵活性高:懒汉模式的单例对象创建时机是可控的,可以根据实际需要进行调整。例如,可以在单例对象创建时传入参数或依赖于外部条件,这增加了单例模式的灵活性。
适用于多种情况:由于懒汉模式可以根据需要创建单例对象,因此它适用于那些单例对象创建成本较高或初始化时间较长的情况。、
缺点
(1)线程安全问题:在多线程环境下,懒汉模式需要确保单例对象的唯一性。如果没有适当的同步机制,可能会出现多个线程同时创建单例对象的情况,导致单例模式失效。因此,需要加锁来确保线程安全,但这会增加性能开销。
(2)性能开销:为了实现线程安全,懒汉模式通常需要在访问单例对象时加锁。虽然这可以确保单例对象的唯一性,但频繁的加锁和解锁操作会降低程序的性能。特别是在高并发场景下,这种性能开销可能更加明显。
(3)双重检查锁定(Double-Checked Locking)的复杂性:为了解决懒汉模式的线程安全问题,同时减少性能开销,可以使用双重检查锁定技术。但这种技术实现起来较为复杂,且容易出错。如果实现不当,可能会导致单例模式失效或引入新的线程安全问题。
//懒汉模式
class Base5
{
public://返回唯一的实例的静态类static Base5& RetuntP(){if (p == nullptr){p = new Base5;}return *p;}//输出信息void Printf(){cout << s << endl;}//释放方法1 --- 实例一个内部类,当内部类释放了,p也会跟着释放class DeleteP{public:~DeleteP(){if (p != nullptr)delete p;}};释放方法二 --- 通过手动调用函数释放//void DeleteP()//{// if (p != nullptr)// delete p;//}private:Base5(){}Base5(const Base5& b) = delete;Base5& operator=(const Base5& b) = delete;private:string s = "hello"; //假设这是储存的信息static Base5* p;};//初始化
Base5* Base5::p = nullptr;//实例一个内部类,当内部类生命周期结束后会释放 p
Base5::DeleteP del;