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
{
    // ....
};
相关推荐
屯子来了1 分钟前
window11 qt5.14.1 msvc2017 编译OpenDDS-3.33.0
c++·qt5·opendds·msvc2017
在屏幕前出油2 分钟前
04. FastAPI——响应类型
开发语言·后端·python·pycharm·fastapi
小郭学习之路2 分钟前
Qt vs tools的一个编译问题
开发语言·qt
前进的李工7 分钟前
LangChain使用之Model IO(提示词模版之FewShotPromptTemplate)
开发语言·人工智能·语言模型·langchain·agent
爱吃生蚝的于勒7 分钟前
【Linux】网络之http协议
linux·运维·服务器·网络·数据结构·c++·http
Ivanqhz7 分钟前
寄存器分配的核心函数 allocate
java·开发语言·后端·python·rust
天远云服9 分钟前
驾培系统车辆核验实战:PHP集成天远二手车估值API实现学员车辆信息自动化管理
大数据·开发语言·自动化·php
2501_945424809 分钟前
高性能计算资源调度
开发语言·c++·算法
fakerth11 分钟前
【Linux】调度器底层原理深入探索
linux·c++·操作系统
野犬寒鸦12 分钟前
JVM垃圾回收机制深度解析(G1篇)(垃圾回收过程及专业名词详解)(补充)
java·服务器·开发语言·jvm·后端·面试