C++特殊类设计
C++特殊类设计
- 1、请设计一个类,不能被拷贝
- 2、请设计一个类,只能在堆上创建对象
- 3、请设计一个类,只能在栈上创建对象
- 4、请设计一个类,不能被继承
- 5、请设计一个类,只能创建一个对象(单例模式)
- 5.1、饿汉模式
- 5.2、懒汉模式
1、请设计一个类,不能被拷贝
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98方式:将拷贝构造和赋值私有
class CopyBan
{
public:
CopyBan() {}
private:
CopyBan(const CopyBan& cb);
CopyBan& operator=(const CopyBan& cb);
};
1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11方式:扩展delete用法
class CopyBan
{
public:
CopyBan() {}
CopyBan(const CopyBan& cb) = delete;
CopyBan& operator=(const CopyBan& cb) = delete;
};
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
2、请设计一个类,只能在堆上创建对象
方式一:析构函数私有
class HeapOnly
{
public:
void Destroy() {
delete this;
}
private:
~HeapOnly()
{}
};
HeapOnly* hp = new HeapOnly;
hp->Destroy();
在栈上创建的对象除了作用域会自动调用析构函数,析构函数私有就无法调用了,所以无法在栈上创建对象。
可以在堆上创建对象,但是有个问题,delete的时候无法调用析构函数,因为delete的原理是析构函数+释放空间,所以我们提供一个Destroy接口,直接在函数内部delete,这样就可以调用析构函数。
方式二:构造函数私有
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
private:
HeapOnly()
{}
HeapOnly(const HeapOnly& hp) = delete;
HeapOnly& operator=(const HeapOnly& hp) = delete;
};
HeapOnly* hp = HeapOnly::CreateObj();
构造函数私有后,既不能在栈上创建,也不能在堆上创建。因此我们需要提供一个CreateObj函数,在里面创建然后将指针返回给外部接收,并且这个函数必须是静态的,否则调不到。
但是这样还是防不住下面这种方式:
HeapOnly* hp = HeapOnly::CreateObj();
HeapOnly copy(*hp);
所以还需要防拷贝。
3、请设计一个类,只能在栈上创建对象
方式:构造函数私有
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly st;
return st;
}
private:
StackOnly()
{}
void* operator new(size_t size) = delete;
};
StackOnly st1 = StackOnly::CreateObj();
构造函数私有,然后提供一个静态成员函数,在该成员函数里面创建对象返回。
但是还是防不住下面这种情况:
StackOnly* hp = new StackOnly(st1);
new的原理就是operator new加构造函数,所以我们需要禁掉operator new。
4、请设计一个类,不能被继承
C++98方式:构造函数私有
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
C++11方式:final修饰
class A final
{
// ...
};
5、请设计一个类,只能创建一个对象(单例模式)
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:饿汉模式和懒汉模式。
5.1、饿汉模式
之前的CreateObj,不管是new还是栈上创建再返回,都是不同的对象,现在需要的是同一个对象,思考一下该怎么做?
namespace hungry
{
class Singleton
{
public:
static Singleton& GetInstance()
{
return _sinst;
}
void Add(const pair<string, string>& kv)
{
_dict[kv.first] = kv.second;
}
void Print()
{
for (const auto& e : _dict)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
private:
Singleton()
{}
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
map<string, string> _dict;
// ...
static Singleton _sinst;
};
Singleton Singleton::_sinst;
}
int main()
{
cout << &hungry::Singleton::GetInstance() << endl;
cout << &hungry::Singleton::GetInstance() << endl;
cout << &hungry::Singleton::GetInstance() << endl;
hungry::Singleton::GetInstance().Add({ "xxx", "111" });
hungry::Singleton::GetInstance().Add({ "yyy", "222" });
hungry::Singleton::GetInstance().Add({ "zzz", "333" });
hungry::Singleton::GetInstance().Print();
return 0;
}
实现:
1、首先将构造函数私有。
2、声明静态类对象,然后在类外定义,提供一个获取对象的静态成员函数GetInstance()。
3、防拷贝,将拷贝和赋值声明为删除。
饿汉模式:就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
但是也有下面的问题:
1、如果单例对象初始化内容很多,影响启动速度。
2、如果有两个单例类,相互依赖关系。假设有A、B两个单例类,要求先创建A,然后再创建B,B的初始化依赖A。
5.2、懒汉模式
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 (const auto& e : _dict)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
private:
Singleton()
{}
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
~Singleton()
{
cout << "~Singleton()" << endl;
// 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);
}
}
map<string, string> _dict;
// ...
static Singleton* _psinst;
};
Singleton* Singleton::_psinst = nullptr;
}
懒汉模式:第一次调用GetInstance的时候创建单例对象
那么如何释放呢?单例一般不用释放,进程结束直接带走了。
特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)
上面的析构函数就是场景2,假设需要把数据写入文件,那么如果是进程结束是不会走析构函数的,无法做到数据持久化。
所以可以提供一个DelIstance函数,中途可以显示释放:
cout << &lazy::Singleton::GetInstance() << endl;
cout << &lazy::Singleton::GetInstance() << endl;
cout << &lazy::Singleton::GetInstance() << endl;
lazy::Singleton::GetInstance().Add({ "xxx", "111" });
lazy::Singleton::GetInstance().Add({ "yyy", "222" });
lazy::Singleton::GetInstance().Add({ "zzz", "333" });
lazy::Singleton::GetInstance().Print();
lazy::Singleton::GetInstance().DelInstance();
那么如果我不想显示释放,并且还希望数据持久化呢?
可以写这么一个类:
class GC
{
public:
~GC()
{
lazy::Singleton::GetInstance().DelInstance();
}
};
GC gc;
还可以直接写在Singleton内部:
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 (const auto& e : _dict)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
class GC
{
public:
~GC()
{
lazy::Singleton::GetInstance().DelInstance();
}
};
private:
Singleton()
{}
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
~Singleton()
{
cout << "~Singleton()" << endl;
// 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);
}
}
map<string, string> _dict;
// ...
static Singleton* _psinst;
static GC _gc;
};
Singleton* Singleton::_psinst = nullptr;
Singleton::GC Singleton::_gc;
}
int main()
{
cout << &lazy::Singleton::GetInstance() << endl;
cout << &lazy::Singleton::GetInstance() << endl;
cout << &lazy::Singleton::GetInstance() << endl;
lazy::Singleton::GetInstance().Add({ "xxx", "111" });
lazy::Singleton::GetInstance().Add({ "yyy", "222" });
lazy::Singleton::GetInstance().Add({ "zzz", "333" });
lazy::Singleton::GetInstance().Print();
return 0;
}