目录
前言
C++作为面向对象语言提供了类,但是这些类有时候不能满足我们的需求,所以就需要我们来设计一些特殊类
一、请设计一个类,不能被拷贝
拷贝会发生在两种场景,拷贝构造,赋值运算符重载,因此我们要对这两个成员函数下手
C++98版本
//设计一个类,不能被拷贝
class A
{
private:
A(const A &a)
{
}
A &operator=(const A &a)
{
}
private:
int _a = 0;
};
可以将这两个函数设为私有,外面就调不到这两个函数,从而禁止拷贝
而C++11 提供了delete关键字,可以禁用这个函数
C++11版本
//设计一个类,不能被拷贝
class A
{
public:
A(const A &a) = delete;
A &operator=(const A &a) = delete;
private:
int _a = 0;
};
二、请设计一个类,只能在堆上创建对象
1、delete析构函数,原本对象出了作用域就会销毁,调用析构函数,删掉之后就调不动析构函数,会报错。
在堆上创建对象,产生的是指针,指针并不会自动调用析构函数,我们只需要想办法回收资源就可以了,可以使用operator delete,但是不要使用delete,因为delete会自动调用析构函数,出现与在栈上创建对象相同的问题了,operator delete不会调用析构函数,如果类内有资源需要释放呢?
我们在类内部写一个与析构函数功能类似的函数,来解决,同时因为对象是new出来的很容易忘记释放,所以在类内的函数也可以加上销毁整个类的功能
//设计一个类只能在堆上创建对象
class B
{
public:
B()
: _b(new int[5])
{
}
~B() = delete;
B(const B &b) = delete;
B &operator=(const B &b) = delete;
void destory()
{
delete[] _b;
operator delete(this);
}
private:
int *_b;
};
2、第二种思路是将构造函数私有,与前面类似,不过这里有一个巨大的Bug,我们虽然第一个对象不能在栈上创建,第二个第三个,可以通过拷贝构造在栈上创建对象,同时我们无法禁用它,在类内将它设为私有,在类外去接受时,会调用拷贝构造
//有Bug版本
//防不住拷贝构造,operator=
class C
{
public:
static C *Create()
{
C *pc = new C;
return pc;
}
private:
C()
{
}
int _c = 0;
};
三、请设计一个类,只能在栈上创建对象
同上,将构造函数私有化,然后设计静态方法创建对象
//设计一个类只能在栈上创建
class D
{
public:
static D Create()
{
D d;
return d;
}
private:
D()
{
}
int _d = 0;
};
四、请设计一个类,不能被继承
1、C++11版本
C++11提供了final关键字,来禁止其它类,去继承该类,在编译时就会报错
//设计一个类不能被继承
c++11版本
class E final
{
public:
private:
int _e = 0;
};
2、C++98版本
C++98并没有什么比较好的方式去实现,派生类去实例化对象时会去调用基类的构造函数,我们只要将基类构造函数设为私有,派生类就调不动基类构造函数,它就会出错,这种方式并不好,因为在实例化对象之前,你并不知道,他能否被继承
class F
{
public:
private:
F()
{
}
int _f = 0;
};
五、请设计一个类,只能创建一个对象
1、设计模式
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
2、单例模式
单例模式(Singleton Pattern)是 C++ 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
(1)饿汉模式
在main之前就创建出对象、
首先它是单例模式,只能有一个对象,所有首先要将他的构造函数设为私有
其次,因为类不能在自己的内部创建对象,所以,需要有一个他自己类型的指针且该指针必须是静态的,一个类有一个,通常单例模式的类内部还要写一个内部类,去进行资源回收,利用类似RAII的思想,利用对象的生命周期去管理资源
//饿汉模式
class HungryManModel
{
private:
HungryManModel()
: _ptr(new char[10])
{
}
char *_ptr = nullptr;
static HungryManModel *_pints;
public:
//内部垃圾回收类
class CGarbo
{
public:
~CGarbo()
{
if (_pints)
{
cout << "delete _pints" << endl;
delete _pints;
}
}
};
};
//
HungryManModel *LazeManMode::_pints = new HungryManModel;
HungryManModel::CGarbo gb;
饿汉模式,单例初始化任务多,会影响程序启动速度,在饿汉没有初始化完成,无法进入main函数
饿汉模式简单且没有线程安全问题,但是在程序有多个单例对象,并且要有先后初始化顺序时,饿汉模式无法控制,因为一个大型项目具有多个文件,虽然能够保证每个饿汉都是在自己文件最先创建,但是在整体上无法控制
(2)懒汉模式
懒汉模式与饿汉模式基本相同
只是对象在要调用时才会初始化,它适用于:单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
class LazeManMode
{
private:
LazeManMode()
: _ptr(new char[10])
{
}
char *_ptr = nullptr;
static LazeManMode *_pints;
public:
//内部垃圾回收类
class CGarbo
{
public:
~CGarbo()
{
if (_pints)
{
cout << "delete _pints" << endl;
delete _pints;
}
}
};
};
//
LazeManMode::CGarbo gb;
void test_LazeManMode()
{
LazeManMode *LazeManMode::_pints = new LazeManMode;
}
懒汉模式能够控制初始化顺序,且不会影响启动速度
它相对复杂,会有线程安全问题
总结
以上就是今天要讲的内容,本文仅仅简单介绍了特殊类的设计