文章目录
- C++
- 特殊类实现
- 1.设计一个类、不能被拷贝
- 2.设计一个类、只能在堆上创建对象
- 3.设计一个类、只能在栈上创建对象
- 4.设计一个类、不能被继承
- 5.设计一个类,只能创建一个对象(单例模式)
- 5.1饿汉模式
- 5.2懒汉模式
C++
特殊类实现
1.设计一个类、不能被拷贝
在C++中,拷贝构造函数和拷贝赋值运算符是两种可以用于创建新对象或为现有对象赋值的方法。
所以,拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
以下是一个示例,展示如何创建一个不能被拷贝的类:
C++98做法:
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
原因:
(1)只声明不定义:因为没有定义,所以该函数根本不会进行任何操作,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
(2)同时设置成私有:这样可以防止直接使用它们, 如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了。
C++11做法:
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
2.设计一个类、只能在堆上创建对象
实现方式:
1.将类的析构函数私有;或者将类的构造函数私有,拷贝构造声明成私有。
2.提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建(单例模式)。
1.1析构函数私有化:
//析构函数私有化,因为堆要手动释放对象
class HeapOnly
{
public:
void Destroy()
{
delete this;
}
private:
~HeapOnly()
{
//...
}
};
int main()
{
//HeapOnly hp1;
//static HeapOnly hp2;
HeapOnly* hp3 = new HeapOnly;
hp3->Destroy();
return 0;
}
原因:
我们可以将析构函数私有化,因为在栈上和静态区的对象需要自动调用析构函数,而析构函数无法显示调用了,就会导致我们无法在栈上和静态区创建对象。
因为堆上的对象是需要我们手动的创建和删除的,所以在堆上创建对象只先调用构造函数; 如果我们需要对堆上创建的对象进行销毁,我们可以提供一个公有函数接口,用这个函数接口调用私有函数。
1.12构造函数私有化:
//2、设计一个类只能在堆上创建对象
//构造函数私有化
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
private:
HeapOnly()
{
//...
}
//防止拷贝构造
HeapOnly(const HeapOnly& hp) = delete;
HeapOnly& operator=(const HeapOnly& hp) = delete;
};
int main()
{
//HeapOnly hp1;
//static HeapOnly hp2;
//HeapOnly* hp3 = new HeapOnly;
HeapOnly* hp3 = HeapOnly::CreateObj();
//HeapOnly copy(*hp3);//拷贝构造在栈上
return 0;
}
原因:
我们将构造函数私有,禁止任何方式创建示例。但是提供一个可以在堆上创建对象的公有函数,这样我们就可以通过公有函数来调用私有的构造函数。
注意:这里的要创建对象的公有函数应该是static修饰的,因为如果要调用公有函数,需要有一个对象示例,而我们要用公有函数创建一个示例,而我们现在没有对象示例,需要调用公有函数(类似鸡生蛋,蛋生鸡)…如果函数在静态区,就可以直接调用了。
同时,为了防止我们创建的对象示例被拷贝构造或者赋值,所以我们还需要将拷贝构造函数和赋值运算符重载函数封死。
3.设计一个类、只能在栈上创建对象
实现方法:
将构造函数私有化,然后设计静态方法创建对象返回即可。
//3、设计一个只能在栈上的类
//构造函数私有
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly st;
return st;
}
private:
StackOnly()
{
//...
}
//对一个类实现专属的operator new
void* operator new(size_t size) = delete;
};
int main()
{
//StackOnly hp1;
//static StackOnly hp2;
//StackOnly* hp3 = new StackOnly;
StackOnly hp3 = StackOnly::CreateObj();
StackOnly copy(hp3);
//new operator new + 构造
//StackOnly* hp4 = new StackOnly(hp3);
return 0;
}
原因:
和上面的实现一样,我们将构造函数私有化,提供一个只能在栈上创建对象的公有函数,static修饰。但是如果我们封死拷贝构造,CreateObj返回的临时对象就无法拷贝给我们的hp3,为了解决我们可以提供一个移动构造。
但是事实上也无法很有效的防止静态区创建对象,所以对于只在栈上创建对象的实现,这样就可以了。
4.设计一个类、不能被继承
C++98方法:
我们将构造函数私有化,派生类中调不到基类的构造函数,则无法继承。
//C++98 私有构造函数
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{
//...
}
}
C++11方法:
final关键字,final修饰类,表示该类不能被继承。
//C++11 final
class A final
{
//...
};
5.设计一个类,只能创建一个对象(单例模式)
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
5.1饿汉模式
饿汉模式:
就是说不管你将来用不用,程序启动时(main函数之前创建)就创建一个唯一的实例对象;
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。
优点:简单;
缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
// 饿汉模式:一开始(main函数之前)就创建单例对象
// 1、如果单例对象初始化内容很多,影响启动速度
// 2、如果两个单例类,互相有依赖关系。
// 假设有A B两个单例类,要求A先创建,B再创建,B的初始化创建依赖A
namespace hungry
{
class Singleton
{
public:
// 2、提供获取单例对象的接口函数
static Singleton& GetInstance()
{
return _sinst;
}
void func();
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<string, string> _dict;
// ...
static Singleton _sinst;
};
Singleton Singleton::_sinst;
void Singleton::func()
{
//
_dict["xxx"] = "1111";
}
}
5.2懒汉模式
懒汉模式:
如果单例对象构造十分耗时或者占用很多资源,比如加载插件, 初始化网络连接,读取文件等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载,在main函数之后创建)更好。
优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制;
缺点:复杂。
namespace lazy
{
class Singleton
{
public:
// 2、提供获取单例对象的接口函数
static Singleton& GetInstance()
{
if (_psinst == nullptr)
{
// 第一次调用GetInstance的时候创建单例对象
_psinst = new Singleton;
}
return *_psinst;
}
// 一般单例不用释放。
// 特殊场景: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;
}
class GC
{
public:
~GC()
{
lazy::Singleton::DelInstance();
}
};
private:
// 1、构造函数私有
Singleton()
{
// ...
}
~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);
}
}
// 3、防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
map<string, string> _dict;
// ...
static Singleton* _psinst;
static GC _gc;
};
Singleton* Singleton::_psinst = nullptr;
Singleton::GC Singleton::_gc;
}