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
{
    // ....
};
相关推荐
lang201509281 天前
Java JSR 250核心注解全解析
java·开发语言
Wpa.wk1 天前
接口自动化测试 - 请求构造和响应断言 -Rest-assure
开发语言·python·测试工具·接口自动化
czhc11400756631 天前
协议 25
java·开发语言·算法
春夜喜雨1 天前
关于内存分配的优化与设计
c++·tcmalloc·malloc·jemallc
ae_zr1 天前
QT动态编译应用后,如何快速获取依赖
开发语言·qt
gjxDaniel1 天前
Kotlin编程语言入门与常见问题
android·开发语言·kotlin
范纹杉想快点毕业1 天前
状态机设计与嵌入式系统开发完整指南从面向过程到面向对象,从理论到实践的全面解析
linux·服务器·数据库·c++·算法·mongodb·mfc
坚定学代码1 天前
认识 ‘using namespace‘
c++
jiang_changsheng1 天前
环境管理工具全景图与深度对比
java·c语言·开发语言·c++·python·r语言
LYOBOYI1231 天前
qml的对象树机制
c++·qt