特殊类的设计

目录

只能再对上创建的类

方法 1

方法 2

只能在栈上创建的类

方法 1

方法 2

单例模式

饿汉模式

懒汉模式

不能被继承的类

方法 1

方法 2



只能再对上创建的类

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

其实有几种方法:

方法 1

  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;
};

方法 2

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

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

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

  4. 但是我们需要提供一个 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();
}
  • 上面这样就可以了,这样的话,也不需要要删除掉拷贝构造函数。

只能在栈上创建的类

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

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

方法 1

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

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

class StackOnly
{
public:
    static StackOnly getObject()
    {
        return StackOnly();
    }
private:
    StackOnly()
    {
        //...
    }
};

方法 2

  • 上面这样就可以了,但是实际上,我们还是可以继续简单一点。

  • 我们是否还记得操作符重载,而 new 调用了 operator new ,那么我们就可以将 operator new 重载,山吃掉,这样就调用不了 new 了。

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

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

单例模式

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

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

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

饿汉模式

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

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

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

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

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

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

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;

懒汉模式

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

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

那么怎么才可以做到呢?

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

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

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

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

  4. 如果不是第一次调用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 函数就可以了。

不能被继承的类

方法 1

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

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

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

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

方法 2

  • 第二种就更简单了。

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

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

你可能感兴趣的:(c++)