目录
只能再对上创建的类
方法 1
方法 2
只能在栈上创建的类
方法 1
方法 2
单例模式
饿汉模式
懒汉模式
不能被继承的类
方法 1
方法 2
如果一个类只能再堆上创建,那么应该怎么创建?
其实有几种方法:
可以将该类的构造函数私有,然后提供一个函数用来获取该类对象的函数。
然后我们可以再该函数中 new 一个该类的对象,然后返回给类的指针。
// 设计一个只能再堆上创建的类
class HeapOnly
{
public:
static HeapOnly* getObject()
{
return new HeapOnly;
}
private:
HeapOnly()
{
//...
}
};
上面为什么要把 getObjext 设计为 static 呢?
因为成员函数的话,调用需要用到 this 指针,但是 this 指针指在对象里面才可以,但是我们现在没有对象,所以我们只能通过 static成员函数来获取对象。
但是上面这样就结束了吗?没有!
如果我们调用拷贝构造呢?
void test1()
{
HeapOnly* hp = HeapOnly::getObject();
HeapOnly hp2(*hp);
}
这样的话,hp2 又创建到栈上了,所以我们还需要将构造函数也私有了,或者删除了。
class HeapOnly
{
public:
static HeapOnly* getObject()
{
return new HeapOnly;
}
private:
HeapOnly()
{
//...
}
HeapOnly(const HeapOnly& hp) = delete;
};
我们第一种是堆构造函数下手,实际上我们还是可以析构函数下手的。
我们将析构函数私有后,然后栈上开辟的对象不能调用析构函数,所以无法定义。
但是我们可以创建在堆上,这样就可以创建成功。
但是我们需要提供一个 destory 函数,我们需要主动的调用这个函数。
class HeapOnly
{
public:
HeapOnly()
{
//...
}
void Destory()
{
this->~HeapOnly();
}
private:
~HeapOnly()
{
//...
}
HeapOnly(const HeapOnly& hp) = delete;
};
测试代码:
void test2()
{
HeapOnly hp;
}
如果是这样的话,那么是不可以的,因为析构函数以及是私有了,所以 hp 无法访问私有的成员函数,所以不可以创建。
但是我们可以创建在对上,那么我们就可以创建成功,当需要析构的时候,我们可以调用 Destroy 函数。
void test2()
{
HeapOnly* hp(new HeapOnly);
hp->Destory();
}
上面这样就可以了,这样的话,也不需要要删除掉拷贝构造函数。
如果想要创建一个只能在栈上创建的类,那么我们应该怎么办呢?
首先我们想一下对析构函数肯定是没办法下手了,那么就只能对构造函数下手。
所以下面,我们还是像上面一样,将构造函数私有。
然后提供一个函数获取该类的对象。
class StackOnly
{
public:
static StackOnly getObject()
{
return StackOnly();
}
private:
StackOnly()
{
//...
}
};
上面这样就可以了,但是实际上,我们还是可以继续简单一点。
我们是否还记得操作符重载,而 new 调用了 operator new ,那么我们就可以将 operator new 重载,山吃掉,这样就调用不了 new 了。
这里再多说一句,如果使用 malloc 开辟的空间它并不能算是对象,所以用 malloc 开辟的空间不作数。
class StackOnly
{
public:
private:
void* operator new(size_t size) = delete;
};
上面也是一种方法,且比较简单。
单例模式就是我们想让该类里面的成员变量只有一份,说简单一点也就是只想要该类只能创建一个对象。
就比如我们的成员变量是一个线程池,或者是内存池等,所以我们并不希望我们又多份这样会极大的占据系统资源。
而单例模式的设计也是分为两种:
饿汉模式就是再该进程启动之前就将所有的资源都准备好了。
所以我们看一下恶汉模式如何设计:
我们可以将该类的构造函数私有,然后提供一个 get 函数,获取该类的对象。
既然我们只想要有一个该类的对象,那么我们可以提前创建一个该类的对象,也就是可以将该类的对象提前创建为一个 static 的一个对象,然后每一次调用就返回该 static 对象的引用。
为了不能拷贝何赋值,我们屏删除掉拷贝构造和赋值重载。
下面我们就看一下设计的具体代码何细节:
class hungry
{
public:
static hungry& getObject()
{
return _single;
}
private:
unordered_map hash;
static hungry _single;
};
我们就可以这样设计,但是 static 的对象需要再类外面初始化:
class hungry
{
public:
static hungry& getObject()
{
return _single;
}
private:
unordered_map hash;
static hungry _single;
};
hungry hungry::_single;
这样可以初始化吗?
hungry 类不是把构造函数私有了吗?
那么这里调用构造会不会失败呢?
其实这里调用构造函数是不会失败的,因为 _single 是类内部的,类内部的成员是可以访问类的私有的没所以这里调用没有任何问题。
为了解释上面的类内的是可以访问类内的私有的,假如我们在类内部声明了一个函数,然后再类外面定义,那么类外面定义的函数就是可以访问到类内部的私有的:
就像下面这个 fun 函数一样。
class hungry
{
public:
static hungry& getObject()
{
return _single;
}
void fun();
private:
unordered_map hash;
static hungry _single;
};
hungry hungry::_single;
void hungry::fun()
{
hash[10] = 20;
}
所以虽然构造函数说私有的,但是 _single 是类内部的,所以可以访问到构造函数。
而这样还不够,我们还需要将构造函数私有,还有我们需要防止拷贝。
class hungry
{
public:
static hungry& getObject()
{
return _single;
}
void fun();
void Add(const pair& kv)
{
hash.insert(kv);
}
void Print()
{
for (auto& kv : hash)
{
cout << kv.first << " " << kv.second << endl;
}
}
private:
hungry()
{
//...
}
//防拷贝
hungry(const hungry& hu) = delete;
hungry& operator=(hungry hu) = delete;
unordered_map hash;
static hungry _single;
};
hungry hungry::_single;
懒汉模式就是当我们需要的时候再创建,而不是再进程启动之前就创建。
这样呢,他就可以让进程启动的快一点。
那么怎么才可以做到呢?
下面看一下懒汉模式的创建:
首先既然是单例模式,那么我们当然不能让该类随便创建,所以我们还是需要将构造函数私有。
然后我们还是可以模仿上面的使用 static 的一个 get 来获取该类的对象。
我们可以将该类的一个成员变量设计为该类的一个指针,如果我们是第一次调用 get 的话,那么我们就需要new 一个该对象,然后赋值给该指针,然后改回该类对象的引用。
如果不是第一次调用get,那么就直接返回该类的对象的引用即可。
下面看一下实现的代码和细节:
//懒汉模式
class lazy
{
public:
static lazy& getObject()
{
if (_single == nullptr)
{
_single = new lazy;
}
return *_single;
}
void fun();
void Add(const pair& kv)
{
hash[kv.first] = kv.second;
}
void Print()
{
for (auto& kv : hash)
{
cout << kv.first << " " << kv.second << endl;
}
}
private:
lazy()
{
//...
}
//防拷贝
lazy(const lazy& hu) = delete;
lazy& operator=(lazy hu) = delete;
unordered_map hash;
static lazy* _single;
};
lazy* lazy::_single = nullptr;
其实这里实现起来和上面基本相同,只不过就是再 get 的时候需要判断一下 _single 是否为空。
上面看起来是结束了,但是还没有,如果这样的话,那么该对象怎析构呢?
或者说是我们想要显示的析构呢?
假设我们想要该类析构的时候帮我们把数据写到文件种:
所以其实我们还是需要提供一个可以析构的函数:
void Destroy()
{
this->~lazy();
}
~lazy()
{
// 析构
FILE* fd = fopen("test.txt", "w");
for (auto& kv : hash)
{
fprintf(fd, "%d", kv.first);
fprintf(fd, "%s", " ");
fprintf(fd, "%d", kv.second);
}
fclose(fd);
}
我们就可以提高这两个函数,然后当我们想要析构的时候,我们就调用 destroy 函数就可以了。
实际上这个是比较简单的。
我们再之前说,如果一个类被继承,那么派生类需要如何初始化基类呢?
组要调用基类的构造函数来初始化,所以根据这个原因,我们可以将基类的构造函数私有。
就像上面的将构造函数私有,然后提高一个获取该类对象的一个函数。
第二种就更简单了。
C++11中有一个关键字 final
被 final 修饰的类就无法被继承。