1.请设计一个类,不能被拷贝
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98的方式(只声明)
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
原因:
-
设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不 能禁止拷贝了
-
只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写 反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11的方式(=delete)
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上
=delete,表示让编译器删除掉该默认成员函数。
2. 请设计一个类,只能在堆上创建对象
实现方式:
-
将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
-
提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
new的需要手动delete,其他两个会自动释放
解决办法1
直接把析构私有,让hp1和hp3无法自动析构
不过会引发hp2无法手动释放
解决办法
解决办法2
这样就会有可以拷贝hp2的情况(拷贝后可能在栈)
解决办法
把拷贝构造和赋值封死
3. 请设计一个类,只能在栈上创建对象
方法:
同上将构造函数私有化,然后设计静态方法创建对象返回即可。
因为这里的拷贝也符合了,所以就会出现以下问题(就可以在堆上开空间了)
重要知识点
解决办法:要先了解new的原理,这里new的时候不可以调用构造,但是还可以调用拷贝构造,但这里不能直接封了拷贝构造,因为StackOnly st用的是拷贝构造,new:会调用两个部分,一部分是构造一部分是operator new,之所以要调用operator new,而不直接调用malloc,是因为operator new会抛异常
4. 请设计一个类,不能被继承
C++98方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
cpp
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
C++11方法(final)
final关键字,final修饰类,表示该类不能被继承。
cpp
class A final
{
// ....
};
5. 请设计一个类,只能创建一个对象(单例模式)
设计模式:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的 总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打 仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后 来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模 式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置 信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再 通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。 单例模式有两种实现模式:
饿汉模式
就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
// 饿汉模式
// 优点:简单
// 缺点:可能会导致进程启动慢(都不知道是在初始化还是系统挂了),且如果有多个单例类对象实例启动顺序不确定。
第一步:构造函数私有(不能随意创建)
把map<string,string>设为私有
创建static变量
类外声明
2、提供获取单例对象的接口函数
创建对象
问题(需要防止拷贝)
然后就会出现一个问题,没有绝对防死可以拷贝构造对象
Singleton copy(Singleton::GetInstance());
第三步防拷贝
添加数据并打印
cpp
饿汉模式:一开始(main函数之前)就创建单例对象
静态的在main之前就创建了
1、如果单例对象初始化内容很多,影响启动速度
2、如果两个单例类,互相有依赖关系。
假设有A B两个单例类,要求A先创建,B再创建,B的初始化创建依赖A
class Singleton
{
public:
// 2、提供获取单例对象的接口函数
static Singleton& GetInstance()//instance是实例的意思
{
return _sinst;
}
//覆盖型的Add
void Add(const pair<string, string>& kv)
{
_dict[kv.first] = kv.second;
}
void Print()
{
for (auto& e : _dict)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
private:
// 1、构造函数私有
Singleton()
{
// ...
}
// 3、防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
//使map全局只有唯一实例
map<string, string> _dict;
// ...
//可以创建自己类型的对象,也不会套娃,因为静态的在静态区
//静态的在main之前就创建了
static Singleton _sinst;
};
Singleton Singleton::_sinst;
int main()
{
//保证创建的都是一个对象
//三个地址都一样,说明三个创建的是同一个
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
cout << &Singleton::GetInstance() << endl;
Singleton copy(Singleton::GetInstance());
//添加数据
Singleton::GetInstance().Add({ "1111","2222" });
Singleton::GetInstance().Print();
return 0;
}
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好。
大问题用懒汉解决
1、如果单例对象初始化内容很多,影响启动速度
2、如果两个单例类,互相有依赖关系。
假设有A B两个单例类,要求A先创建,B再创建,B的初始化创建依赖A,就没法用饿汉了,然后就有了下面的懒汉
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取 文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化, 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
// 懒汉
// 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控 制。
// 缺点:复杂
//懒汉用的static指针,main之前创建一个指针不耽误时间
//顺序可以随便控制
//单例一般不用智能指针,所以需要显示释放
// 特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)
改进
cpp
//懒汉用的static指针,main之前创建一个指针不耽误时间
//顺序可以随便控制
//单例一般不用智能指针,所以需要显示释放
// 特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)
namespace lazy
{
class Singleton
{
public:
//改进::2
// 2、提供获取单例对象的接口函数
static Singleton& GetInstance()
{
if (_psinst == nullptr)
{
// 第一次调用GetInstance的时候创建单例对象
_psinst = new Singleton;
}
return *_psinst;
}
//改进::3
// 一般单例不用释放。
// 特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)
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& e : _dict)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
//改进::5,因为一个单例显示释放一次多个就要多次delete太麻烦
//所以有了GC
//用智能指针又没办法显示释放
class GC
{
public:
~GC()
{
lazy::Singleton::DelInstance();
}
};
private:
// 1、构造函数私有
Singleton()
{
// ...
}
//程序结束后,把东西写入文件中(可持久化)
~Singleton()
{
cout << "~Singleton()" << endl;
//改进::4
// map数据写到文件中,可持久化
FILE* fin = fopen("map.txt", "w");
for (auto& e : _dict)
{
fputs(e.first.c_str(), fin);
fputs(":", fin);
fputs(e.second.c_str(), fin);
fputs("\n", fin);
}
}
// 3、防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
map<string, string> _dict;
// ...
//改进::这里弄一个指针
static Singleton* _psinst;
//改进::5
static GC _gc;
};
//类外定义
Singleton* Singleton::_psinst = nullptr;
//改进::5
Singleton::GC Singleton::_gc;
}
int main()
{
//Singleton s1;
//Singleton s2;
cout << &lazy::Singleton::GetInstance() << endl;
cout << &lazy::Singleton::GetInstance() << endl;
cout << &lazy::Singleton::GetInstance() << endl;
//Singleton copy(Singleton::GetInstance());
lazy::Singleton::GetInstance().Add({ "xxx", "111" });
lazy::Singleton::GetInstance().Add({ "yyy", "222" });
lazy::Singleton::GetInstance().Add({ "zzz", "333" });
lazy::Singleton::GetInstance().Add({ "abc", "333" });
lazy::Singleton::GetInstance().Print();
//lazy::Singleton::DelInstance();
lazy::Singleton::GetInstance().Add({ "abc", "444" });
lazy::Singleton::GetInstance().Print();
//lazy::Singleton::DelInstance();
return 0;
}