C++ 常见特殊类的设计(含有单例模式)

一、设计一个不能被拷贝的类

1.1 为什么需要一些不能被拷贝的类呢?

很多情况下如果允许进行拷贝逻辑是说不通的,比如IO流有缓冲区如何进行拷贝,又比如说对于线程这个类,线程对象已经运行了如何进行拷贝?

想要让某个类不能被拷贝直接将拷贝构造函数和operator=封死就可以解决了。

1.2 策略

C++98中的做法是:

将构造函数私有化并只声明不定义

cpp 复制代码
class CopyBan
{
public:
    //...
private:
    CopyBan(const CopyBan& cb);
    CopyBan& operator=(const CopyBan& cb);
    //...
};

这种私有化+只声明不定义的策略是因为:

现来说一下私有化,私有化防止进行该类实例化出来的对象执行拷贝和复制重载;只声明不定义是因为该函数不可能被调用,将这两个函数进行定义也没有任何意义,如果不声明的话会自动在publish中生成默认构造函数就会允许拷贝构造和赋值重载了。

C++11中的做法是:

通过在函数后面加上 delete关键字(准确的说是加上 = delete)

cpp 复制代码
class CopyBan
{
public:
    CopyBan(const CopyBan& cb) = delete;
    CopyBan operator=(const CopyBan& cb) = delete;
private:
    //...
};

通过使用 = delete 让编译器进行删除该类的构造函数。

二、设计一个类只能在堆上进行创建对象

2.1 策略

构造函数私有化

要想让一个类只能在堆上进行创建空间,也就是说需要将传统的通过构造函数进行在栈上进行创建对象的这种方式进行封锁,然后进行提供提供一个静态的公共函数接口,专门用来提供在对上进行创建对象的这种需求。

cpp 复制代码
class HeapOnly
{
public:
    static HeapOnly* CreateObject()
    {
        return new HeapOnly;
    }
private:
    HeapOnly()
    {
    }
    HeapOnly(const HeapOnly& ho) = delete;
    HeapOnly& operator=(const HeapOnly& ho) = delete; 
};

这里说明一下为什么也要将拷贝构造和赋值重载进行封死?

如果要不封死,虽然不能通过构造函数进行在栈上进行创建对象,但是可以先通过在堆上进行创建对象再通过拷贝构造或者赋值重载在栈上进行创建。

还有一种非常特别的思路

将析构函数进行私有化

cpp 复制代码
class HeapOnly
{
public:
    HeapOnly()
    {

    }
    void DeleteObject()
    {
        delete this;
    }
private:
    HeapOnly(const HeapOnly& ho) = delete;
    HeapOnly& operator=(const HeapOnly& ho) = delete;
    ~HeapOnly()
    {

    }
};

将析构函数进行私有化这种策略是利用了编译器的性质,当编译器识别到在栈上进行创建对象时,会考虑到该对象的作用域,超过作用域会将该对象进行销毁并进行资源释放,当将析构函数进行私有化时,此时编译器在进行识别到在栈上进行创建对象时,发现析构函数定义在了private中意味着不能进行在类外调用,此时就会报错,不允许在堆上进行创建对象。

三、设计一个类只能在栈上进行创建对象

3.1 策略

先说一种错误的策略:

构造函数私有化

想要将一个设计成只能在栈上进行创建对象,首先肯定要禁止 new 进行创建对象,new 关键字进行创建对象本质上又在类外调用了构造函数,所以将类的构造函数进行私有化即可屏蔽在类外进行通过new进行创建对象,但是还要在栈上能创建对象,所以还需要在类中的public中进行定义一个创建对象的静态成员函数。

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

    }
    StackOnly(const StackOnly& so) = delete;
    StackOnly& operator=(const StackOnly& so) = delete;
};

这样进行实验是确实可以通过编译的,但是这样进行构建的对象是死的,无法将 CreateObject 函数进行创建出来的对象进行赋值给外面进行接收的对象,这些都是在c++17之前;但是c++17之后是允许通过编译的,之所以能够通过编译是因为这个return StackOnly()这行代码在c++17之前是先进行创建临时对象将临时对象拷贝或者移动到外面接收的变量中,但是c++17之后直接将拿着这个对象中的成员一个一个赋值给外面对象的成员,既然没有临时对象也就不在涉及拷贝构造。

这里能编译通过也是很坑的,举个例子:

cpp 复制代码
StackOnly s = StackOnly::CreateObject(10); // ✅ RVO 让你直接在栈上建好了 s
std::vector<StackOnly> vec;
vec.push_back(s); // ❌ 这里报错

vector容器中存储的元素是在堆上,当通过vec对象进行调用push_back想要将s对象进行存储到容器中时,此时会报错,因为:push_back并不会直接收纳s本身,是在栈上容器存储的是在堆上,真正的逻辑是:

在堆上找到一个空位

然后调用s的拷贝构造函数将s中的数据进行拷贝到堆上

正确的方式

将 new 和 delete 系类的函数禁用

cpp 复制代码
class StackOnly
{
public:
    StackOnly()
    {

    }
    ~StackOnly()
    {

    }
    static void* operator new(size_t sz) = delete;
    static void operator delete(void* memy) = delete;
    static void* operator new[](size_t sz) = delete;
    static void operator delete[](void* memy) = delete;
};

四、设计一个类不能被继承

4.1 策略

c++98 做法:将构造函数私有化

将基类的构造函数私有化,当子类进行继承基类时并且进行定义对象时,此时该对象会进行调用基类和子类的构造函数进行初始化,但是基类中构造函数私有了,调不到从而做到基类无法被继承。

cpp 复制代码
class NonInherit
{
public:
    static NonInherit GetInstance()
    {
        return NonInherit();
    }
private:
    NonInherit()
    {}
};

c++11 做法:利用 final 关键字进行修饰

cpp 复制代码
class NonInherit final
{
    // ....
};
相关推荐
lly2024061 小时前
Bootstrap 警告框
开发语言
2601_949146531 小时前
C语言语音通知接口接入教程:如何使用C语言直接调用语音预警API
c语言·开发语言
曹牧2 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
在路上看风景2 小时前
19. 成员初始化列表和初始化对象
c++
KYGALYX2 小时前
服务异步通信
开发语言·后端·微服务·ruby
zmzb01032 小时前
C++课后习题训练记录Day98
开发语言·c++
念风零壹2 小时前
C++ 内存避坑指南:如何用移动语义和智能指针解决“深拷贝”与“内存泄漏”
c++
猫头虎3 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
YUJIANYUE3 小时前
PHP纹路验证码
开发语言·php
仟濹3 小时前
【Java基础】多态 | 打卡day2
java·开发语言