特殊类的设计

目录

只能再对上创建的类

[方法 1](#方法 1)

[方法 2](#方法 2)

只能在栈上创建的类

[方法 1](#方法 1)

[方法 2](#方法 2)

单例模式

饿汉模式

懒汉模式

不能被继承的类

[方法 1](#方法 1)

[方法 2](#方法 2)



只能再对上创建的类

  • 如果一个类只能再堆上创建,那么应该怎么创建?

其实有几种方法:

方法 1

  1. 可以将该类的构造函数私有,然后提供一个函数用来获取该类对象的函数。

  2. 然后我们可以再该函数中 new 一个该类的对象,然后返回给类的指针。

cpp 复制代码
// 设计一个只能再堆上创建的类
class HeapOnly
{
public:
    static HeapOnly* getObject()
    {
        return new HeapOnly;
    }
private:
    HeapOnly()
    {
        //...
    }
};

上面为什么要把 getObjext 设计为 static 呢?

因为成员函数的话,调用需要用到 this 指针,但是 this 指针指在对象里面才可以,但是我们现在没有对象,所以我们只能通过 static成员函数来获取对象。

但是上面这样就结束了吗?没有!

如果我们调用拷贝构造呢?

cpp 复制代码
void test1()
{
    HeapOnly* hp = HeapOnly::getObject();
​
    HeapOnly hp2(*hp);
}

这样的话,hp2 又创建到栈上了,所以我们还需要将构造函数也私有了,或者删除了。

cpp 复制代码
class HeapOnly
{
public:
    static HeapOnly* getObject()
    {
        return new HeapOnly;
    }
private:
    HeapOnly()
    {
        //...
    }
​
    HeapOnly(const HeapOnly& hp) = delete;
};

方法 2

  1. 我们第一种是堆构造函数下手,实际上我们还是可以析构函数下手的。

  2. 我们将析构函数私有后,然后栈上开辟的对象不能调用析构函数,所以无法定义。

  3. 但是我们可以创建在堆上,这样就可以创建成功。

  4. 但是我们需要提供一个 destory 函数,我们需要主动的调用这个函数。

cpp 复制代码
class HeapOnly
{
public:
    HeapOnly()
    {
        //...
    }
​
    void Destory()
    {
        this->~HeapOnly();
    }
​
private:
    ~HeapOnly()
    {
        //...
    }
​
    HeapOnly(const HeapOnly& hp) = delete;
};

测试代码:

cpp 复制代码
void test2()
{
    HeapOnly hp;
}
  • 如果是这样的话,那么是不可以的,因为析构函数以及是私有了,所以 hp 无法访问私有的成员函数,所以不可以创建。

  • 但是我们可以创建在对上,那么我们就可以创建成功,当需要析构的时候,我们可以调用 Destroy 函数。

cpp 复制代码
void test2()
{
    HeapOnly* hp(new HeapOnly);
    hp->Destory();
}
  • 上面这样就可以了,这样的话,也不需要要删除掉拷贝构造函数。

只能在栈上创建的类

  • 如果想要创建一个只能在栈上创建的类,那么我们应该怎么办呢?

  • 首先我们想一下对析构函数肯定是没办法下手了,那么就只能对构造函数下手。

方法 1

  1. 所以下面,我们还是像上面一样,将构造函数私有。

  2. 然后提供一个函数获取该类的对象。

cpp 复制代码
class StackOnly
{
public:
    static StackOnly getObject()
    {
        return StackOnly();
    }
private:
    StackOnly()
    {
        //...
    }
};

方法 2

  • 上面这样就可以了,但是实际上,我们还是可以继续简单一点。
  • 我们是否还记得操作符重载,而 new 调用了 operator new ,那么我们就可以将 operator new 重载,山吃掉,这样就调用不了 new 了。

  • 这里再多说一句,如果使用 malloc 开辟的空间它并不能算是对象,所以用 malloc 开辟的空间不作数。

cpp 复制代码
class StackOnly
{
public:
private:
​
    void* operator new(size_t size) = delete;
};
  • 上面也是一种方法,且比较简单。

单例模式

单例模式就是我们想让该类里面的成员变量只有一份,说简单一点也就是只想要该类只能创建一个对象。

就比如我们的成员变量是一个线程池,或者是内存池等,所以我们并不希望我们又多份这样会极大的占据系统资源。

而单例模式的设计也是分为两种:

饿汉模式

  • 饿汉模式就是再该进程启动之前就将所有的资源都准备好了。

所以我们看一下恶汉模式如何设计:

  1. 我们可以将该类的构造函数私有,然后提供一个 get 函数,获取该类的对象。

  2. 既然我们只想要有一个该类的对象,那么我们可以提前创建一个该类的对象,也就是可以将该类的对象提前创建为一个 static 的一个对象,然后每一次调用就返回该 static 对象的引用。

  3. 为了不能拷贝何赋值,我们屏删除掉拷贝构造和赋值重载。

下面我们就看一下设计的具体代码何细节:

cpp 复制代码
class hungry
{
public:
    static hungry& getObject()
    {
        return _single;
    }
​
private:
    unordered_map<int, int> hash;
​
    static hungry _single;
};
  • 我们就可以这样设计,但是 static 的对象需要再类外面初始化:
cpp 复制代码
class hungry
{
public:
    static hungry& getObject()
    {
        return _single;
    }
​
private:
    unordered_map<int, int> hash;
​
    static hungry _single;
};
hungry hungry::_single;
  • 这样可以初始化吗?

  • hungry 类不是把构造函数私有了吗?

  • 那么这里调用构造会不会失败呢?

  • 其实这里调用构造函数是不会失败的,因为 _single 是类内部的,类内部的成员是可以访问类的私有的没所以这里调用没有任何问题。

  • 为了解释上面的类内的是可以访问类内的私有的,假如我们在类内部声明了一个函数,然后再类外面定义,那么类外面定义的函数就是可以访问到类内部的私有的:

    就像下面这个 fun 函数一样。

cpp 复制代码
class hungry
{
public:
    static hungry& getObject()
    {
        return _single;
    }
​
    void fun();
​
private:
    unordered_map<int, int> hash;
​
    static hungry _single;
};
hungry hungry::_single;
​
void hungry::fun()
{
    hash[10] = 20;
}
  • 所以虽然构造函数说私有的,但是 _single 是类内部的,所以可以访问到构造函数。

  • 而这样还不够,我们还需要将构造函数私有,还有我们需要防止拷贝。

cpp 复制代码
class hungry
{
public:
    static hungry& getObject()
    {
        return _single;
    }
​
    void fun();
​
    void Add(const pair<int, int>& 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<int, int> hash;
​
    static hungry _single;
};
hungry hungry::_single;

懒汉模式

  • 懒汉模式就是当我们需要的时候再创建,而不是再进程启动之前就创建。

  • 这样呢,他就可以让进程启动的快一点。

那么怎么才可以做到呢?

下面看一下懒汉模式的创建:

  1. 首先既然是单例模式,那么我们当然不能让该类随便创建,所以我们还是需要将构造函数私有。

  2. 然后我们还是可以模仿上面的使用 static 的一个 get 来获取该类的对象。

  3. 我们可以将该类的一个成员变量设计为该类的一个指针,如果我们是第一次调用 get 的话,那么我们就需要new 一个该对象,然后赋值给该指针,然后改回该类对象的引用。

  4. 如果不是第一次调用get,那么就直接返回该类的对象的引用即可。

下面看一下实现的代码和细节:

cpp 复制代码
//懒汉模式
class lazy
{
public:
    static lazy& getObject()
    {
        if (_single == nullptr)
        {
            _single = new lazy;
        }
​
        return *_single;
    }
​
    void fun();
​
    void Add(const pair<int, int>& 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<int, int> hash;
​
    static lazy* _single;
};
lazy* lazy::_single = nullptr;
  • 其实这里实现起来和上面基本相同,只不过就是再 get 的时候需要判断一下 _single 是否为空。

上面看起来是结束了,但是还没有,如果这样的话,那么该对象怎析构呢?

或者说是我们想要显示的析构呢?

假设我们想要该类析构的时候帮我们把数据写到文件种:

所以其实我们还是需要提供一个可以析构的函数:

cpp 复制代码
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 函数就可以了。

不能被继承的类

方法 1

  • 实际上这个是比较简单的。

  • 我们再之前说,如果一个类被继承,那么派生类需要如何初始化基类呢?

  • 组要调用基类的构造函数来初始化,所以根据这个原因,我们可以将基类的构造函数私有。

  • 就像上面的将构造函数私有,然后提高一个获取该类对象的一个函数。

方法 2

  • 第二种就更简单了。

  • C++11中有一个关键字 final

  • 被 final 修饰的类就无法被继承。

相关推荐
看到我,请让我去学习9 分钟前
OpenCV编程- (图像基础处理:噪声、滤波、直方图与边缘检测)
c语言·c++·人工智能·opencv·计算机视觉
xiaolang_8616_wjl9 小时前
c++文字游戏_闯关打怪2.0(开源)
开发语言·c++·开源
夜月yeyue9 小时前
设计模式分析
linux·c++·stm32·单片机·嵌入式硬件
无小道9 小时前
c++-引用(包括完美转发,移动构造,万能引用)
c语言·开发语言·汇编·c++
FirstFrost --sy11 小时前
数据结构之二叉树
c语言·数据结构·c++·算法·链表·深度优先·广度优先
Tanecious.11 小时前
C++--map和set的使用
开发语言·c++
Yingye Zhu(HPXXZYY)12 小时前
Codeforces 2021 C Those Who Are With Us
数据结构·c++·算法
liulilittle13 小时前
LinkedList 链表数据结构实现 (OPENPPP2)
开发语言·数据结构·c++·链表
无聊的小坏坏13 小时前
三种方法详解最长回文子串问题
c++·算法·回文串
山河木马13 小时前
前端学习C++之:.h(.hpp)与.cpp文件
前端·javascript·c++