【C++庖丁解牛】C++特殊类设计 | 单例模式

CSDN 2024-08-06 17:35:01 阅读 100

🍁你好,我是 RO-BERRY

📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识

🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油


在这里插入图片描述


目录

1.请设计一个类,不能被拷贝2. 请设计一个类,只能在堆上创建对象方案一: 析构函数私有化方案二:构造函数私有

3. 请设计一个类,只能在栈上创建对象将构造函数私有化,然后设计静态方法创建对象返回即可

4. 请设计一个类,不能被继承C++98版本C++11版本

5. 请设计一个类,只能创建一个对象(单例模式)


1.请设计一个类,不能被拷贝

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

C++98:将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。

<code>class CopyBan

{ -- -->

// ...

private:

CopyBan(const CopyBan&);

CopyBan& operator=(const CopyBan&);

//...

};

原因:

设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不

能禁止拷贝了只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写

反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

C++11:C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

class CopyBan

{

// ...

CopyBan(const CopyBan&)=delete;

CopyBan& operator=(const CopyBan&)=delete;

//...

};


2. 请设计一个类,只能在堆上创建对象

方案一: 析构函数私有化

class HeapOnly

{

public:

void Destroy()

{

delete this;

}

private:

~HeapOnly(){ }

};

析构函数私有化意味着该函数仅能通过对象自身(即堆上的实例)访问,外部无法直接调用。如果析构函数是公有的,那么在栈上创建的对象,在其生命周期结束时(如函数返回或局部变量超出作用域),系统会自动调用析构函数释放资源。然而,栈内存的生命周期受到特定上下文的限制,一旦函数返回或对象离开作用域,栈上的对象就会销毁,这时析构函数也就失去了作用。

当析构函数设计为私有,只有堆上的对象可以存在,因为它们的生命周期不受函数范围限制。程序员需要显式地使用new关键字将对象分配到堆上,以便在适当的时候手动调用析构函数。这允许开发者更精确地控制资源管理,比如在堆对象的整个生命周期内执行清理操作。

因此,私有化的析构函数确保了只对那些在堆上分配的对象进行定制的析构处理,避免了栈上对象意外调用析构的问题。

方案二:构造函数私有

实现方式:

将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

class HeapOnly

{

public:

static HeapOnly* CreateObject()

{

return new HeapOnly;

}

private:

HeapOnly() { }

// C++98

// 1.只声明,不实现。因为实现可能会很麻烦,而你本身不需要

// 2.声明成私有

//HeapOnly(const HeapOnly&);

// or

// C++11

HeapOnly(const HeapOnly&) = delete;

};

构造函数私有化意味着该类的对象不能通过new关键字直接在栈上创建。当我们试图这样做时,由于构造函数无法直接被外部访问,编译器会拒绝这种做法,因为它找不到合适的初始化路径。

当构造函数是私有的,我们通常需要一个公共的静态方法(如上述例子中的CreateObject())来作为创建对象的入口点。这种方式允许我们在堆内存中创建对象,因为静态变量默认存储在数据段的BSS部分,属于堆的一部分。

这样做的目的是为了限制对象的数量,提高系统的控制能力和资源利用率。例如,在内存有限的情况下,或者为了保证在整个程序范围内仅有一份共享资源,我们会选择这样的设计。通过这种方法,对象的创建、销毁以及状态管理都在类内部进行,外部用户只能通过提供的接口进行操作。


3. 请设计一个类,只能在栈上创建对象

将构造函数私有化,然后设计静态方法创建对象返回即可

class StackOnly

{

public:

static StackOnly CreateObj()

{

return StackOnly();

}

// 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉

// StackOnly obj = StackOnly::CreateObj();

// StackOnly* ptr3 = new StackOnly(obj);

void* operator new(size_t size) = delete;

void operator delete(void* p) = delete;

private:

StackOnly()

:_a(0)

{ }

private:

int _a;

};

因为它禁止了 operator new 和 operator delete 的使用,这两个成员函数通常用于动态内存分配。

通过删除 new 和 delete 操作符,强迫用户只能通过 CreateObj() 静态方法来创建对象,这个方法会直接在栈上分配内存,确保对象始终位于栈上。


4. 请设计一个类,不能被继承

C++98版本

// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承

class NonInherit

{

public:

static NonInherit GetInstance()

{

return NonInherit();

}

private:

NonInherit()

{ }

};

C++11版本

final关键字,final修饰类,表示该类不能被继承。

class A final

{

// ....

};


5. 请设计一个类,只能创建一个对象(单例模式)

设计模式:

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模

式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

单例模式:

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个

访问它的全局访问点,该实例被所有程序模块共享。 比如在某个服务器程序中,该服务器的配置

信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再

通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现模式:

饿汉模式 : 就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象

优点:简单

缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。

class Singleton

{

public:

static Singleton* GetInstance()

{

return &m_instance;

}

private:

// 构造函数私有

Singleton() { };

// C++98 防拷贝

Singleton(Singleton const&);

Singleton& operator=(Singleton const&);

// or

// C++11

Singleton(Singleton const&) = delete;

Singleton& operator=(Singleton const&) = delete;

static Singleton m_instance;

};

Singleton Singleton::m_instance; // 在程序入口之前就完成单例对象的初始化

GetInstance() 函数是公开的静态成员函数,每次请求实例时都会返回指向同一个实例的指针,即 m_instance。由于构造函数被声明为私有的,外部无法直接创建新的 Singleton 对象。

私有部分包括两个重载的拷贝构造函数和赋值操作符,都被声明为 delete 或者禁止,这防止了用户复制单例实例,进一步保证了其唯一性。

最后,在程序入口处 Singleton Singleton::m_instance; 创建并初始化了单例实例。由于是在类定义之前初始化的,保证了在整个程序生命周期内只有一个实例。

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

懒汉模式

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

class Singleton

{

public:

static Singleton* GetInstance()

{

if (nullptr == m_pInstance)

{

m_pInstance = new Singleton();

}

return m_pInstance;

}

// 实现一个内嵌垃圾回收类

class CGarbo {

public:

~CGarbo() {

if (Singleton::m_pInstance)

delete Singleton::m_pInstance;

}

};

// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象

static CGarbo Garbo;

private:

// 构造函数私有

Singleton() { };

// 防拷贝

Singleton(Singleton const&);

Singleton& operator=(Singleton const&);

static Singleton* m_pInstance; // 单例对象指针

};

Singleton* Singleton::m_pInstance = nullptr;

Singleton::CGarbo Garbo;



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。