🌎特殊类设计与设计模式
文章目录:
🚀特殊类设计
✈️设计一个只能在堆上创建对象的类
如果只能在堆上创建对象,也就意味着在创建对象的时候必须使用new来创建对象,那么我们就需要:
解决方式一:
- 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
- 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
cpp
class HeapOnly {
public:
static HeapOnly* GetHeapOnlyObj(int x, int y)
{
return new HeapOnly(x, y);
}
// 调用拷贝与赋值会发生值拷贝,出现在栈上开辟的对象,故需要禁用
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
private:
HeapOnly()
{}
HeapOnly(int x, int y)
:_x(x), _y(y)
{}
int _x;
int _y;
};
int main()
{
HeapOnly *ho = HeapOnly::GetHeapOnlyObj(1, 2);// 获取对象 返回new出来的对象,必定是在堆上开辟
return 0;
}
除了将构造函数私有以外,我们知道,如果没有实现拷贝构造与赋值重载,类会默认生成,并且都是以值拷贝的方式对另一个变量初始化、赋值,所以为了避免这种情况,我们 需要将拷贝构造与赋值重载禁用。
解决方式二:
不一定非要把拷贝与赋值重载禁用,我们也可以把析构函数屏蔽或者禁用,但是把析构函数私有化了,最好在实现一个可调用析构函数的接口,这样创建对象只能使用new来创建对象:
cpp
class HeapOnly {
public:
HeapOnly()
{}
HeapOnly(int x, int y)
:_x(x), _y(y)
{}
void Destroy()
{
delete this;
}
private:
~HeapOnly()
{
cout << "~HeapOnly()" << endl;
}
int _x;
int _y;
};
int main()
{
HeapOnly ho1;
HeapOnly ho2 = new HeapOnly(1, 2);
return 0;
}
如果使用智能指针来管理,直接使用智能指针是无法编译通过的:
cpp
int main()
{
shared_ptr<HeapOnly> ptr(new HeapOnly(1, 2));
return 0;
}
实际上,这是因为我们把析构函数私有化,而 shared_ptr指针底层默认的 删除器 调用的是delete ,但是现在delete调用不了,所以编译不通过,所以我们 需要自定义删除器,可以使用仿函数,但是这里更推荐使用lambda表达式来做删除器:
cpp
int main()
{
shared_ptr<HeapOnly> ptr(new HeapOnly(1, 2),
[](HeapOnly* ptr) { ptr->Destroy(); }
);
return 0;
}
✈️设计一个只能在栈上创建对象的类
同理,与在堆上创建对象原理相似,将构造函数私有化,在实现一个构造对象的public成员函数:
cpp
// 请设计一个类,只能在栈上创建对象
class StackOnly {
public:
static StackOnly GetStackOnlyObj(int x, int y)
{
return StackOnly(x, y);
}
StackOnly(const StackOnly&) = delete;
StackOnly& operator=(const StackOnly&) = delete;
private:
StackOnly()
{}
StackOnly(int x, int y)
:_x(x), _y(y)
{}
int _x;
int _y;
};
int main()
{
StackOnly so = StackOnly::GetStackOnlyObj(1, 2);
return 0;
}
这里编不过的原因是因为,我们实现的获取栈上的对象是值拷贝,返回的是局部对象,出了作用域就会销毁,所以我们需要调用拷贝构造,因为我们不能将拷贝构造delete掉:
cpp
// 请设计一个类,只能在栈上创建对象
class StackOnly {
public:
static StackOnly GetStackOnlyObj(int x, int y)
{
return StackOnly(x, y);
}
// StackOnly(const StackOnly&) = delete;
StackOnly& operator=(const StackOnly&) = delete;
private:
StackOnly()
{}
StackOnly(int x, int y)
:_x(x), _y(y)
{}
int _x;
int _y;
};
int main()
{
// HeapOnly *ho = HeapOnly::GetHeapOnlyObj(1, 2);// 获取对象 返回new出来的对象,必定是在堆上开辟
StackOnly so = StackOnly::GetStackOnlyObj(1, 2);
return 0;
}
这个时候就可以编译通过了,这里我们把拷贝构造给放开了,就有可能会导致创建的对象是在堆上开辟的:
因为 new在底层调用void* operator new(size_t size)函数,只需将该函数屏蔽掉或者禁用掉即可, 同理 delete尽量也要禁用一下:
cpp
// 请设计一个类,只能在栈上创建对象
class StackOnly {
public:
static StackOnly GetStackOnlyObj(int x, int y)
{
return StackOnly(x, y);
}
// 重载类内专属new关键字,将来在调用时会优先调用类内new,这里将其禁用就杜绝了在堆上创建对象的可能
void* operator new(size_t n) = delete;
void operator delete(void* ptr) = delete;
StackOnly& operator=(const StackOnly&) = delete;
private:
StackOnly()
{}
StackOnly(int x, int y)
:_x(x), _y(y)
{}
int _x;
int _y;
};
int main()
{
StackOnly so = StackOnly::GetStackOnlyObj(1, 2);
StackOnly* so2 = new StackOnly(so);// 在堆上开辟空间, 开辟错误
return 0;
}
✈️请设计一个不能被拷贝的类
很简单,只需要将拷贝构造和赋值重载私有化或者禁用即可:
cpp
class NoCopy {
public:
NoCopy()
{}
// NoCopy(const NoCopy&) = delete;
// NoCopy& operator=(const NoCopy&) = delete;
private:
NoCopy(const NoCopy&)
{}
No5Copy& operator=(const NoCopy&)
{}5
};
✈️请设计一个不能被继承的类
我们知道一个类如果可以被继承,那么当子类进行初始化时,会优先调用父类构造初始化父类部分,那么我们可以按照这个思路:
解决方案一 : 构造函数私有化
cpp
class NonInherit {
public:
static GetNonInheritObj()
{
return NonInherit();
}
private:
NonInherit()
{}
};
这样就可以避免构造函数被调用,从而避免被继承。
解决方案二:使用 final 关键字
cpp
class NonInherit final
{
public:
NonInherit()
{}
static GetNonInheritObj()
{
return NonInherit();
}
private:
};
该关键字是C++11提出的关键字,可以禁止继承。
🚀设计模式
-
设计模式(Design Pattern) 是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
-
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
-
单例模式 : 设计模式的一种,一个类只能创建一个对象(当前进程中有且只有一个),即单例模式。该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
而单例模式分类又为 饿汉式 与 懒汉式:
✈️饿汉模式
什么是饿汉模式呢?简单来说就是:不管你将来用不用,程序启动时就创建一个唯一的实例对象。
饿汉模式要求在开始main函数之前这个对象就存在有且只有一份,所以我们可以把需要的数据资源存放在类内。
首先,一定要将构造函数给私有化,这样外部就没法随意调用构造函数创建对象,但是我们要在public区建立一个接口,返回私有对象的地址或引用,并且将此接口设置为静态,这样我们可以使用类域访问。
那么由此接口使用的对象都会是同一个对象:
cpp
class Singleton
{
public:
static Singleton* GetInstance()
{
return &_sint;
}
void Print()
{
cout << "x: " << _x << endl;
cout << "y: " << _y << endl;
for (auto &e : _vstr)
{
cout << e << " ";
}
cout << endl;
}
Singlenton(const Singlenton&) = delete;// 禁用赋值与拷贝
Singlenton& operator=(const Singlenton&) = delete;
private:
Singleton(int x = 0, int y = 0, vector<string> vstr = { "xxxx", "yyyy" })
:_x(x)
, _y(y)
, _vstr(vstr)
{}
int _x;
int _y;
vector<string> _vstr;
// 静态成员变量存在静态区, 属于全局的,声明在类中
static Singleton _sint;
};
// 定义和初始化
Singleton Singleton::_sint(1, 2, {"today", "I'm sad"});
int main()
{
Singleton::GetInstance()->Print();
return 0;
}
类内私有设置一个私有静态对象,这个对象实际不存在类中,但是需要类域访问,而静态成员的声明和定义分离,声明在类中,定义在类外。这样想要使用这个单例我们就需要从类域拿到GetInstance()获取单例,由单例获取成员方法工作,同时需要禁止复制与拷贝,防止使用单例再构造对象。
看起来饿汉模式的单类模式非常实用,实际上它有以下 缺点:
- 如果单例对象的数据比较多,构造初始化成本比较高,那么会影响程序启动的速度。迟迟进入不了main函数。
- 多个单例类有初始化启动依赖关系,饿汉无法控制(假设A和B两个单例,需要A先初始化,B在初始化,但是我们无法保证哪个单例先创建)。
✈️懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用 懒汉模式(延迟加载)更好。
懒汉模式也非常简单,在饿汉模式的基础上,将单例对象更改为单例指针,这样我们就可以在GetInstance()初始化了,当第一次调用GetInstance时,就可以创建一个单例对象,而当再次调用时就为同一单例,这样就保证了单例只会在调用的时候出现。
cpp
class Singleton
{
public:
static Singleton* GetInstance(int x = 3, int y = 4, vector<string> vstr = {"It's so", " bad!"})
{
// 第一次调用才产生单例
if (_psint == nullptr)
{
_psint = new Singleton(x, y, vstr);
}
return _psint;
}
void Print()
{
cout << "x: " << _x << endl;
cout << "y: " << _y << endl;
for (auto& e : _vstr)
{
cout << e << " ";
}
cout << endl;
}
private:
Singleton(int x = 0, int y = 0, vector<string> vstr = { "xxxx", "yyyy" })
:_x(x)
, _y(y)
, _vstr(vstr)
{}
int _x;
int _y;
vector<string> _vstr;
// 静态成员变量存在静态区, 属于全局的,定义在类中
static Singleton *_psint;
};
// 初始化
Singleton* Singleton::_psint = nullptr;
int main()
{
// hanger::Singleton::GetInstance()->Print();
lazy::Singleton::GetInstance(5, 6)->Print();
return 0;
}
这样的单例模式并不是一个较为完整的单例模式,因为我们没有释放资源,而析构函数一定不能被显示调用,所以也需要放在类私有部分,那么我们就需要在public部分实现一个接口,让接口回调类内析构。
cpp
class Singleton
{
public:
static Singleton* GetInstance(int x = 3, int y = 4, vector<string> vstr = {"It's so", " bad!"})
{
// 第一次调用才产生单例
if (_psint == nullptr)
{
_psint = new Singleton(x, y, vstr);
}
return _psint;
}
void Print()
{
cout << "x: " << _x << endl;
cout << "y: " << _y << endl;
for (auto& e : _vstr)
{
cout << e << " ";
}
cout << endl;
}
void DelInstance()
{
delete Singleton::_psint;
}
private:
~Singleton()
{
cout << "~Singleton" << endl;
}
Singleton(int x = 0, int y = 0, vector<string> vstr = { "xxxx", "yyyy" })
:_x(x)
, _y(y)
, _vstr(vstr)
{}
int _x;
int _y;
vector<string> _vstr;
// 静态成员变量存在静态区, 属于全局的,定义在类中
static Singleton *_psint;
};
// 初始化
Singleton* Singleton::_psint = nullptr;
int main()
{
// hanger::Singleton::GetInstance()->Print();
lazy::Singleton::GetInstance(5, 6)->Print();
return 0;
}
但是这样我们只能手动调用了,很容易导致程序员忘记手动添加删除资源,所以我们想要实现一个可以自动释放资源的类,我们可以考虑内部类。在public区域,我们实现一个辅助删除类,类内只有自己的析构函数,而析构函数的作用是调用DelInstance(),而我们在private区域定义一个辅助类的静态对象,当main函数结束时,static生命周期也就到了,会自动调用析构函数,这样就可以调用DelInstance()函数清理懒汉的单例模式了。
cpp
class Singleton
{
public:
static Singleton* GetInstance(int x = 3, int y = 4, vector<string> vstr = {"It's so", " bad!"})
{
if (_psint == nullptr)
{
_psint = new Singleton(x, y, vstr);
}
return _psint;
}
void Print()
{
cout << "x: " << _x << endl;
cout << "y: " << _y << endl;
for (auto& e : _vstr)
{
cout << e << " ";
}
cout << endl;
}
static void DelInstance()
{
if (Singleton::_psint)
{
delete Singleton::_psint;
}
}
class AssisDele
{
public:
~AssisDele()
{
Singleton::DelInstance();
}
};
private:
~Singleton()
{
cout << "~Singleton()" << endl;
}
Singleton(int x = 0, int y = 0, vector<string> vstr = { "xxxx", "yyyy" })
:_x(x)
, _y(y)
, _vstr(vstr)
{}
static AssisDele _ad;
int _x;
int _y;
vector<string> _vstr;
// 静态成员变量存在静态区, 属于全局的,定义在类中
static Singleton *_psint;
};
// 初始化
Singleton* Singleton::_psint = nullptr;
Singleton::AssisDele _ad;
int main()
{
lazy::Singleton::GetInstance(5, 6)->Print();
return 0;
}
懒汉模式缺点:
- 懒汉模式在调用GetInstance()时需要加锁(C++线程安全篇详解),不然可能多个线程同时调用生成多个对象。
- 实现起来比较复杂。