【C++】特殊类的设计

这篇具有很好参考价值的文章主要介绍了【C++】特殊类的设计。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


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

💕 C++98方式:

在C++11之前,想要一个一个类不被拷贝,只有将拷贝构造函数定义为私有,这样在类外就不能调用拷贝构造函数来构造对象了。但是在类内还是可以调用拷贝构造函数来构造对象。

【C++】特殊类的设计,C++,c++,开发语言,设计模式

所以正确的做法是 将拷贝构造函数定义为私有,同时拷贝构造函数只声明,不实现。这样即使在类中掉哦那个了拷贝构造函数,编译器也会将错误检查出来。

class CopyBan
{
public:
	CopyBan()
	{
		_ptr = new char[10]{ 0 };
	}
	~CopyBan()
	{
		delete[] _ptr;
	}

	void func()
	{
		CopyBan tmp(*this);
	}
	
private:
	// 重写深拷贝构造函数
	CopyBan(const CopyBan& cb);
	char* _ptr;
};

【C++】特殊类的设计,C++,c++,开发语言,设计模式


💕 C++11方式:

C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。 同时,这种方法也不再需要将拷贝构造函数定义为私有。

class CopyBan
{
public:
	CopyBan()
	{
		_ptr = new char[10]{ 0 };
	}
	~CopyBan()
	{
		delete[] _ptr;
	}

	CopyBan(const CopyBan& cb) = delete;

private:
	char* _ptr;
};

【C++】特殊类的设计,C++,c++,开发语言,设计模式


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

💕 C++98方式:

C++98中构造函数私有化,派生类中调不到基类的构造函数,则无法调用父类的构造函数完成父类成员的初始化工作,从而达到父类不能被继承的效果。

class NonInherit
{
public:
	static NonInherit GetInstance()
	{
		return NonInherit();
	}
private:
	NonInherit()
	{}
};
class subClass : public NonInherit
{
public:
	subClass()
	{}
private:
	int _a = 0;
};

【C++】特殊类的设计,C++,c++,开发语言,设计模式


💕 C++11方式:

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

class NonInherit final
{
public:
	static NonInherit GetInstance()
	{
		return NonInherit();
	}
private:
	NonInherit()
	{}
};

【C++】特殊类的设计,C++,c++,开发语言,设计模式


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

一般的类可以在三个不同的存储位置创建对象:

  • 在栈上创建对象,对象出了局部作用域自动销毁
  • 通过new关键字在堆上创建对象,对象出了局部作用域不会自动销毁,需要我们手动销毁。否则则会发生内存泄漏
  • 通过static关键字在静态区创建对象,对象的作用域为定义时所在的局部域,而对象的生命周期伴随着整个进程,这个对象在mian调用结束后由操作系统自动回收

💕 方法一:构造函数私有化

将构造函数声明为私有,同时删除拷贝构造函数,然后提供一个静态成员函数,在该静态成员函数中完成堆对象的创建。

class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}
	HeapOnly(const HeapOnly& ho) = delete;
private:
	HeapOnly()
	{}
};

静态成员没有this指针,所以可以通过类名+域作用限定符来进行调用而不需要通过对象调用。同时我们还需要删除拷贝构造函数,防止在类外通过下面这种取巧的方式来创建栈区或者堆区对象:

HeapOnly* pho1 = HeapOnly::CreateObj();
HeapOnly pho2(*pho1); // 通过拷贝构造函数在栈上创建对象
static HeapOnly ho3(*pho1); // 通过拷贝构造函数在静态区上创建对象

💕 方法二:析构函数私有化

将析构函数私有化,同时提供一个专门的成员函数,在该成员函数中完成堆对象的析构

class HeapOnly
{
public:
	HeapOnly()
	{}

	void Destroy()
	{
		this->~HeapOnly();
	}
private:
	~HeapOnly()
	{}
};

对于在堆上创建的对象,编译器并不会主动调用析构函数来回收资源,而是由用户手动进行delete或者进程退出后由操作系统回收,所以编译器并不会报错。但对于自定义类型的对象,delete会首先调用其析构函数完成兑现资源的清理,然后再调用operator delete 释放对象的空间,所以这里我们不能使用delete关键字来手动释放new出来的对象,因为调用析构函数会失败。

所以我们需要一个Destroy成员函数,通过它来调用析构函数完成资源的清理。这个Destroy函数不需要声明为静态类型,因为只有类的对象才需要调用它。最后,我们也不需要再删除拷贝构造函数了,因为拷贝出来的栈对象或者静态对象压根儿无法创建出来。

【C++】特殊类的设计,C++,c++,开发语言,设计模式


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

💕 方法一:在类中禁用operator new 和 operator delete函数

new和delete是C++中的关键字,其底层通过调用operator new 和operator delete函数来开辟和释放空间。

operator new 和 operator delete 函数是普通的全局函数,而并非运算符重载,它们的函数名就长这样罢了。因此,我们可以在类中重载operator new和 operator delete 函数,然后将他们声明为删除函数,这样就不能通过new和delete再堆上创建对象了,但是我们仍然可以在静态区创建对象,与类的要求不符。

class StackOnly
{
public:
	StackOnly(int x = 0)
		:_x(x)
	{}
	~StackOnly()
	{}
	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
private:
	int _x;
};

【C++】特殊类的设计,C++,c++,开发语言,设计模式


💕 方法二:构造函数私有化,提供一个在栈上创建对象的静态成员函数

这种方式和设计一个 只能在堆上创建对象的类的思路是一样的。但是不能删除拷贝构造函数,否则就不能通过下面这种方式构造栈对象了。

StackOnly st = StackOnly::CreateObj();

但是不禁用拷贝构造函数又会导致可以通过拷贝构造函数创建出静态区上的对象,所以我们设计出的只能在栈上创建对象的类是有缺陷的。

class StackOnly
{
public:
	static StackOnly CreateObj(int x)
	{
		return StackOnly(x);
	}
private:
	StackOnly(int x = 0)
		:_x(x)
	{}
	int _x;
};

int main()
{
	StackOnly st1 = StackOnly::CreateObj(1);

	return 0;
}

【C++】特殊类的设计,C++,c++,开发语言,设计模式


4. 创建一个类, 只能创建一个对象(单例模式)

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

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

单例模式:

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现模式,饿汉模式懒汉模式


饿汉模式

饿汉模式是将构造函数私有,然后删除拷贝构造函数和赋值运算符重载函数,由于单例模式全局只允许有一个唯一对象,所以我们可以定义一个静态类对象作为类的承运,然后提供一个GetInstance接口来获取这个静态类对象。静态类对象需要在类内声明,类外定义,定义时需要指定类域。同时,GetInstance接口也必须是静态函数。

饿汉模式的特点是在类加载的时候就创建单例对象,因此其实梨花在程序运行之前(main函数调用之前就已经完成)实现如下:

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		return _ins;
	}

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

private:
	// 限制在类外面随意创建对象
	Singleton()
	{}
	
	static Singleton* _ins;
};

Singleton* Singleton::_ins = new Singleton;

因为饿汉模式在main函数前就被创建,所以它不存在线程安全问题,但是它也存在一些缺点

  • 有的单例对象构造十分耗时或者需要占用很多资源,比如加载插件、 初始化网络连接、读取文件等等,会导致程序启动时加载速度慢。
  • 饿汉模式在程序启动时就创建了单例对象,所以即使在程序运行期间并没有用到该对象,它也会一直存在于内存中,浪费了一定的系统资源。
  • 多个单例类存在初始化依赖关系时,饿汉模式无法控制。比如A、B两个单例类存在于不同的文件中,我们要求先初始化A,再初始化B,但是A、B谁先启动初始化是由OS自动进行调度控制的,我们无法进行控制。

多线程模式下的饿汉模式:

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		return _ins;
	}
	void Add(const string& str)
	{
		_mtx.lock();
		_v.push_back(str);
		_mtx.unlock();
	}
	void Print()
	{
		_mtx.lock();
		for (auto& e : _v)
			cout << e << endl;
		cout << endl;
		_mtx.unlock();
	}

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

private:
	// 限制在类外面随意创建对象
	Singleton()
	{}
	vector<string> _v;
	static Singleton* _ins;
	mutex _mtx;
};

Singleton* Singleton::_ins = new Singleton;

int main()
{
	srand(time(0));

	int n = 100;
	thread t1([n]() {
		for (size_t i = 0; i < n; ++i)
		{
			Singleton::GetInstance()->Add("t1线程:" + to_string(rand()));
		}
	});

	thread t2([n]() {
		for (size_t i = 0; i < n; ++i)
		{
			Singleton::GetInstance()->Add("t2线程:" + to_string(rand()));
		}
	});

	t1.join();
	t2.join();
	Singleton::GetInstance()->Print();
	return 0;
}

【C++】特殊类的设计,C++,c++,开发语言,设计模式


懒汉模式

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

懒汉模式 是另一种实现单例模式的方式,与饿汉模式不同的是:懒汉模式是延迟示例化的,即在第一次访问时才创建唯一的实例。

懒汉模式的实现思路是将构造函数私有化,然后提供一个静态私有指针成员来保存唯一实例的地址,并通过一个公共的静态方法来获取该实例。

class Singleton
{
public:
	static Singleton& GetInstance()
	{
		// 双检查加锁
		if (_ins == nullptr)
		{
			_smtx.lock();
			if(_ins == nullptr)
				_ins = new Singleton;
			_smtx.unlock();
		}
		return *_ins;
	}

	static void DelInstance()
	{
		_smtx.lock();
		if (_ins) {
			delete _ins;
			_ins = nullptr;
		}
		_smtx.unlock();
	}

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

	~Singleton()
	{
		// 持久化
		// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好
	}
private:
	// 限制在类外面随意创建对象
	Singleton()
	{}
	static Singleton* _ins;
	static mutex _smtx;
};

Singleton* Singleton::_ins = nullptr;
mutex Singleton::_smtx;

懒汉模式下的线程安全问题以及双检查加锁

懒汉模式也引入了新的问题:单例对象的创建线程是不安全的。对于懒汉模式来说,由于其单例对象是在第一次使用时才创建的,那么在多线程模式下,就有可能会存在多个线程并行/并发的去执行 _psins = new Singleton 语句,从而导致前面创建出来单例对象指针被后面的覆盖,最终发生内存泄露。

【C++】特殊类的设计,C++,c++,开发语言,设计模式

单例对象的资源释放与保存问题

一般来说单例对象都是不需要考虑释放的,因为不管是饿汉模式还是懒汉模式,单例对象都是全局的,全局资源在程序结束后会被自动回收 (进程退出后OS会解除进程地址空间与物理内存的映射)。但是我们也可以手动对其进行回收。需要注意的是,有时我们需要在回收资源之前将资源的相关数据保存到文件中,这种情况下我们就必须手动回收了。

  1. 在类中定义一个静态的 DelInstance接口,来回收与保存资源。
static void DelInstance()
{
	_smtx.lock();
	if (_ins) {
		delete _ins;
		_ins = nullptr;
	}
	_smtx.unlock();
}
  1. 定义一个内部的GC类,通过Singleton类中的一个静态GC类对象,使得程序在结束回收GC对象时自动调用GC类的析构从而完成资源回收与数据保存工作。避免我们忘记调用Dellnstance接口而丢失数据。

例如:

class Singleton
{
public:
	static Singleton& GetInstance()
	{
		if (_ins == nullptr)
		{
			// 双检查加锁
			_smtx.lock();
			if(_ins == nullptr)
				_ins = new Singleton;
			_smtx.unlock();
		}
		return *_ins;
	}

	void Add(const string& str)
	{
		_mtx.lock();
		_v.push_back(str);
		_mtx.unlock();
	}

	void Print()
	{
		_mtx.lock();
		for (auto& e : _v)
			cout << e << endl;
		cout << endl;
		_mtx.unlock();
	}

	static void DelInstance()
	{
		_smtx.lock();
		if (_ins) {
			delete _ins;
			_ins = nullptr;
		}
		_smtx.unlock();
	}

	// 单例对象回收
	class GC
	{
	public:
		~GC()
		{
			DelInstance();
		}
	};
	
	static GC _gc;

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

	~Singleton()
	{
		// 持久化
		// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好
	}
private:
	// 限制在类外面随意创建对象
	Singleton()
	{}
	vector<string> _v;
	static Singleton* _ins;
	mutex _mtx;
	static mutex _smtx;
};

Singleton* Singleton::_ins = nullptr;
mutex Singleton::_smtx;
Singleton::GC Singleton::_gc;

【C++】特殊类的设计,C++,c++,开发语言,设计模式


懒汉模式的一种简单实现方式

class Singleton
{
public:
	static Singleton& GetInstance()
	{
		static Singleton sins;
		return sins;
	}
	Singleton(const Singleton& sin) = delete;
	Singleton& operator=(const Singleton& sin) = delete;
private:
	Singleton()
	{}
};

上面这种实现方式的缺点就是不稳定,因为只有在 C++11 及其之后的标准中局部静态对象的初始化才是线程安全的,而在 C++11 之前的版本中并不能保证。文章来源地址https://www.toymoban.com/news/detail-724860.html

到了这里,关于【C++】特殊类的设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】特殊类设计(单例模式)

    设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。 使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 根本原因是为了代码复用,增加可维护性。 设计模式的例子:迭代器模式 拷贝一共就只有两个场景,一

    2023年04月22日
    浏览(56)
  • 【C++】特殊类设计+单例模式+类型转换

    需要云服务器等云产品来学习Linux的同学可以移步/--腾讯云--/--阿里云--/--华为云--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。   目录 一、设计一个类,不能被拷贝 1、C++98 2、C++11 二、设计一个类,只能在堆上创建对象 1、将构造设为私有 2、将析构设为

    2024年02月06日
    浏览(54)
  • 【C++】特殊类的设计

    💕 C++98方式: 在C++11之前,想要一个一个类不被拷贝,只有将 拷贝构造函数 定义为私有,这样在类外就不能调用拷贝构造函数来构造对象了。但是在类内还是可以调用拷贝构造函数来构造对象。 所以正确的做法是 将拷贝构造函数定义为私有,同时拷贝构造函数只声明,不

    2024年02月07日
    浏览(45)
  • C++进阶 特殊类的设计

    本篇博客介绍:介绍几种特殊的类 我们的拷贝只会发生在两个场景当中 拷贝构造函数 赋值运算符重载 所以说我们只需要让类失去 或者说不能使用这两个函数即可 这里有两个解决方案 在C++98中 我们将拷贝构造函数只声明不定义 并且将其访问权限设置为私有即可 原因如下

    2024年02月12日
    浏览(40)
  • C++之特殊类的设计

    目录 一、单例模式 1、设计模式 2、单例模式 1、饿汉模式 2、懒汉模式 3、单例对象的释放问题 二、设计一个不能被拷贝的类 三、设计一个只能在堆上创建对象的类 四、设计一个只能在栈上创建对象的类 五、设计一个不能被继承的类 概念: 设计模式(Design Pattern)是一套被

    2024年02月08日
    浏览(44)
  • 【C++】特殊类的设计 | 类型转换

    设计模式是 被反复使用 多数人知晓 经过分类的、代码设计经验的总结 单例模式: 一个类只能创建一个对象 即单例模式,该模式可以保证系统中该类只有一个实例 单例模式分为饿汉模式和懒汉模式 饿汉模式 一开始就创建对象(main函数之前) 假设想要vector数组全局只有一份

    2024年02月15日
    浏览(37)
  • C++:特殊类的设计和类型转换

    1.设计一个类,不能被拷贝 拷贝只会放生在两个场景中: 拷贝构造函数以及赋值运算符重载 ,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 2.设计一个类,只能在堆上创建对象 两种实现方式: 将类的 构造函数私有 , 拷贝构造声

    2024年01月24日
    浏览(55)
  • C++特殊类的设计与类型转换

    通过new创建的类就是堆上的。 方法一: 这里主要以封禁构造函数为主,让外部只能通过调用func函数方式去创建对象,func函数的内部是通过new创建的,这里要注意的就是拷贝构造的问题。 赋值重载不用删除,因为需要现有一个对象才能赋值给另一个对象,上面的代码只会创

    2024年02月08日
    浏览(41)
  • 【重点:单例模式】特殊类设计

    方式如下: 将构造函数设置为私有,防止外部直接调用构造函数在栈上创建对象。 向外部提供一个获取对象的static接口,该接口在堆上创建一个对象并返回。 将拷贝构造函数设置为私有,并且只声明不实现,防止外部调用拷贝构造函数在栈上创建对象。 说明一下: 向外部

    2024年02月15日
    浏览(47)
  • 【C++】特殊类的设计(只在堆、栈创建对象,单例对象)

    🌏博客主页: 主页 🔖系列专栏: C++ ❤️感谢大家点赞👍收藏⭐评论✍️ 😍期待与大家一起进步! 实现方式: 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建 实现方法:

    2024年02月06日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包