目录
[1 设计一个只能在堆上创建的类](#1 设计一个只能在堆上创建的类)
[2 设计一个只能在栈上创建的类](#2 设计一个只能在栈上创建的类)
[4 设计一个不能被继承的类](#4 设计一个不能被继承的类)
[5 设计一个只能被创建一次的类](#5 设计一个只能被创建一次的类)
前言:
类的种类繁多,面对不同的场景衍生出了不同的类,每个类各有特点,比如有的类不能被拷贝,有的类不能在堆上创建,有的类不能只能在堆上创建。
那么今天,我们就来介绍一些特殊的类。
1 设计一个只能在堆上创建的类
只能在堆上创建也就是只能通过new的方式来创建,那么我们肯定是不能让编译器调用默认构造函数的,调用了就代表是栈上创建的。
那么我们一定要将构造函数私有,那么我们通过了new的方式创建了,但是仍然可以通过拷贝的方式,创建一个在栈上的对象,所以我们同时要禁止拷贝构造的使用,C++98的方式是只声明且不实现,并且设为私有,C++11的方式就简单多了,直接delete。
当然了,既然构造可以私有化,析构也可以,这里不过多阐述,简单知道即可。
那么我们如何通过new的方式创建呢?这里就需要用到静态函数了,因为如果不设置静态函数,我们甚至连函数都访问不了,因为对象还没有创建,所以需要静态函数,通过静态函数来创建对象:
cpp
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
private:
HeapOnly()
{}
};
int main()
{
HeapOnly* h1 = HeapOnly::CreateObj();
return 0;
}
2 设计一个只能在栈上创建的类
同上文一样,设计一个只能在栈上创建的,还是从构造函数入手,可以发现,对类有点特殊要求的,在构造函数上面动的操作比较多。
思想是一样的,私有构造,成员函数返回对象,但是这里并没有起到禁止new的效果,我们知道,new的底层是operator new + 抛异常,虽然我们不能直接的动new,但是我们可以间接的,比如禁止operator new 和 operator delete:
首先,第一种方法是直接私有构造函数:
cpp
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
}
private:
StackOnly()
{}
};
这样就new不出来了。
cpp
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
}
void* operator new(size_t) = delete;
void operator delete(void*) = delete;
StackOnly()
{}
private:
};
也可以直接构造 但是要禁止operator new,这种方式比较奇葩,,了解一下。
4 设计一个不能被继承的类
这个就很简单了,直接final安排就可以,当然,C++98里面还是将构造函数私有了,这样也访问不到了:
cpp
class Base final
{
public:
private:
int _a;
int _b;
};
class Derive : public Base
{
};
5 设计一个只能被创建一次的类
这里是本文的重点,这是一种单例模式,属于一种设计模式,我们在此之前接触过许多设计模式,一个是适配器模式,比如function bind都是一种适配器等,还有迭代器模式什么的。
今天介绍的是单例模式,表示一个类只能创建一次,那么创建一次的意思是这个类实例化出来的对象是全局的,并且不管再怎么实例化,都只能是最开始的那个对象。
这里涉及的模式有两种,一个是饿汉模式,一个是懒汉模式。
饿汉模式
饿汉模式的核心思想是在main函数之前就将对象创建好,那么谁比main函数还早呢?
全局对象。
全局对象的创建在main之前,那么如何保证实例化多次仍然是同一个对象呢?
静态变量。
所以我们的操作为将构造私有,利用全局和静态提前创建好一个对象。
cpp
class ConfigInfo
{
public:
static ConfigInfo* GetInfo()
{
return &_info;
}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
ConfigInfo()
{}
static ConfigInfo _info;
};
ConfigInfo ConfigInfo::_info;
int main()
{
cout << ConfigInfo::GetInfo() << endl;
cout << ConfigInfo::GetInfo() << endl;
cout << ConfigInfo::GetInfo() << endl;
return 0;
}
那么饿汉模式有两个问题,如果单例模式的类很多呢?在main函数之前就要将所有的单例模式的类全部实例化完成,这就会导致程序启动慢,这个其实还好。
如果是两个单例模式的类互相依赖,A启动了之后,B才能启动,万一进main之前B先实例化了呢?那么就死循环了,程序最后崩溃了就。
所以现在就需要用到懒汉模式。
懒汉模式
懒汉模式的核心思想是,调用了再初始化。
这就可以完美解决上面的所有问题了,你说全部实例化,我是运行时确定,你说依赖,我可以决定哪个优先调用。
那么懒汉模式怎么实现呢?
cpp
class ConfigInfo
{
public:
static ConfigInfo* GetInstance()
{
// C++11之前也能保证线程安全
// 多线程调用需要考虑线程安全问题
// 双检查加锁
if (_spInfo == nullptr) // 性能
{
unique_lock<mutex> lock(_mtx);
if (_spInfo == nullptr) // 线程安全
{
_spInfo = new ConfigInfo;
}
}
return _spInfo;
}
private:
ConfigInfo()
{
cout << "ConfigInfo()" << endl;
}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
static ConfigInfo* _spInfo;
static mutex _mtx;
};
ConfigInfo* ConfigInfo::_spInfo = nullptr;
mutex ConfigInfo::_mtx;
首先拷贝和赋值重载都是要delete的,其次就是为了保证线程安全,需要锁,但是创建一次之后,就不用进锁了,直接判空就可以。这里主要还是线程安全问题,最外层的检查是为了性能问题。
感谢阅读!