C++中将构造函数或析构函数定义为private

2023-10-30

        今天面试被问到了这个单例模式常用到的技术手段,下面进行分析:

        很多情况下要求当前的程序中只有一个object。例如一个程序只有一个和数据库的连接,只有一个鼠标的object。通常我们都将构造函数的声明置于public区段,假如我们将其放入private区段中会发生什么样的后果?这意味着什么?

        当我们在程序中声明一个对象时,编译器为调用构造函数(如果有的话),而这个调用将通常是外部的,也就是说它不属于class对象本身的调用,假如构造函数是私有的,由于在class外部不允许访问私有成员,所以这将导致编译出错。

        然而,对于class本身,可以利用它的static公有成员,因为它们独立于class对象之外,不必产生对象也可以使用它们。

        此时因为构造函数被class私有化,所以我们要创建出对象,就必须能够访问到class的私有域;这一点只有class的成员可以做得到;但在我们建构出其对象之前,怎么能利用它的成员呢? static公有成员,它是独立于class对象而存在的,“我们”可以访问得到。假如在某个static函数中创建了该class的对象,并以引用或者指针的形式将其返回(这里不以对象返回,主要是构造函数是私有的,外部不能创建临时对象),就获得了这个对象的使用权。

下面是例子:

class OnlyHeapClass
{
public:
	static OnlyHeapClass* GetInstance()
	{
		// 创建一个OnlyHeapClass对象并返回其指针
		return (new OnlyHeapClass);
	}
	void Destroy();
private:
	OnlyHeapClass() { }
	~OnlyHeapClass() {}
};

int main()
{
	OnlyHeapClass *p = OnlyHeapClass::GetInstance();
	... // 使用*p
	delete p;
	return 0;
}
 
        这个例子使用了私有构造函数,GetInstance()作为OnlyHeapClass的静态成员函数来在内存中创建对象:由于要跨函数传递并且不能使用值传递方式,所以我们选择在堆上创建对象,这样即使getInstance()退出,对象也不会随之释放,可以手动释放。

        构造函数私有化的类的设计保证了其他类不能从这个类派生或者创建类的实例,还有这样的用途:例如,实现这样一个class:它在内存中至多存在一个,或者指定数量个的对象(可以在class的私有域中添加一个static类型的计数器,它的初值置为0,然后在GetInstance()中作些限制:每次调用它时先检查计数器的值是否已经达到对象个数的上限值,如果是则产生错误,否则才new出新的对象,同时将计数器的值增1.最后,为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,复制构造函数也要特别声明并置为私有。

        如果将构造函数设计成Protected,也可以实现同样的目的,但是可以被继承。

        另外如何保证只能在堆上new一个新的类对象呢?只需把析构函数定义为私有成员。

        原因是C++是一个静态绑定的语言。在编译过程中,所有的非虚函数调用都必须分析完成。即使是虚函数,也需检查可访问性。因些,当在栈上生成对象时,对象会自动析构,也就说析构函数必须可以访问。而堆上生成对象,由于析构时机由程序员控制,所以不一定需要析构函数。保证了不能在栈上生成对象后,需要证明能在堆上生成它。这里OnlyHeapClass与一般对象唯一的区别在于它的析构函数为私有。delete操作会调用析构函数。所以不能编译。

        那么如何释放它呢?答案也很简单,提供一个成员函数,完成delete操作。在成员函数中,析构函数是可以访问的。当然detele操作也是可以编译通过。
void OnlyHeapClass::Destroy() {
	delete this;
}

        构造函数私有化的类的设计可以保证只能用new命令在堆中来生成对象,只能动态的去创建对象,这样可以自由的控制对象的生命周期。但是,这样的类需要提供创建和撤销的公共接口。

        另外重载delete,new为私有可以达到要求对象创建于栈上的目的,用placement new也可以创建在栈上。

补充:
1.为什么要自己调用呢?对象结束生存期时不就自动调用析构函数了吗?什么情况下需要自己调用析构函数呢?   

        比如这样一种情况,你希望在析构之前必须做一些事情,但是用你类的人并不知道, 那么你就可以重新写一个函数,里面把要做的事情全部做完了再调用析构函数。 这样人家只能调用你这个函数析构对象,从而保证了析构前一定会做你要求的动作。

2.什么情况下才用得着只生成堆对象呢? 

        堆对象就是new出来的,相对于栈对象而言。什么情况下要new,什么情况下在栈里面 提前分配,无非就是何时该用动态,何时该用静态生成的问题。这个要根据具体情况具体分析。比如你在一个函数里面事先知道某个对象最多只可能10个,那么你就可以 定义这个对象的一个数组。10个元素,每个元素都是一个栈对象。如果你无法确定数 字,那么你就可以定义一个这个对象的指针,需要创建的时候就new出来,并且用list 或者vector管理起来。 


        类中“私有”权限的含义就是:私有成员只能在类域内被访问,不能在类域外进行访问。

        把析构函数定义为私有的,就阻止了用户在类域外对析构函数的使用。这表现在如下两个方面:

        1. 禁止用户对此类型的变量进行定义,即禁止在栈内存空间内创建此类型的对象。要创建对象,只能用 new 在堆上进行。

        2. 禁止用户在程序中使用 delete 删除此类型对象。对象的删除只能在类内实现,也就是说只有类的实现者才有可能实现对对象的 delete,用户不能随便删除对象。如果用户想删除对象的话,只能按照类的实现者提供的方法进行。

        可见,这样做之后大大限制了用户对此类的使用。一般来说不要这样做;通常这样做是用来达到特殊的目的,比如在 singleton 的实现上。

        stackoverflow上面有这方面的说明用例(详情:http://stackoverflow.com/questions/1008019/c-singleton-design-pattern

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++中将构造函数或析构函数定义为private 的相关文章

随机推荐

  • Error during WebSocket handshake: Unexpected response code: 404,springboot整合websocket出错

    Error during WebSocket handshake Unexpected response code 404 浏览器访问websocket出现错误 一 运行环境 二 需要引入的包 三 项目路径 四 工具类 五 静态页面以及js
  • CPU一级缓存L1 D-cache\L1 I-cache与二级缓存L2 cache深度分析

    CPU缓存 通过优化的的读取机制 可以使CPU读取缓存的命中率非常高 大多数CPU可达90 左右 也就是说CPU下一次要读取的数据90 都在缓存 SRAM 中 只有大约10 需要从内存 DRAM DDR等 读取 这大大节省了CPU直接读取内
  • 算法篇:贪心算法解决田忌赛马问题

    田忌赛马 贪心算法 问题分析 这是一道很经典的贪心算法入门题 这道题贪心的思想是 要把每一匹马的作用发挥到最大 把已 方赢的概率增加到最大 我是从双方慢马的角度来分析的 其实快马和慢马的思路差不多 用田忌最慢的马与王最慢的马相比较 1 如果
  • Spring 中如何为Bean注入集合呢?

    转自 Spring 中如何为Bean注入集合呢 下文讲述Spring中为Bean注入集合的方法分享 如下所示 常见的集合类型有 List Set Map 和 properties 标签 集合名称 说明
  • DC-DC电源转换电路设计

    第1条 搞懂DC DC电源怎么回事 DC DC电源电路 又称为DC DC转换电路 其主要功能就是进行输入输出电压转换 一般我们把输入电源电压在72V以内的电压变换过程称为DC DC转换 常见的电源主要分为车载与通讯系列和通用工业与消费系列
  • 【ES】多字段聚合分析

    public static Map
  • Vscode python配置了numpy包之后无法调用

    如果之前已经在vscode中配置好了numpy等其他库并且运行成功了 突然换了一个文件打开 如果发现找不到numpy库 很大可能是vscode将你的python解释器给更换了 如上所示 除了自己安装的python解释器之外 还有内置的和其他
  • 《QDebug 2023年6月》

    一 Qt Widgets 问题交流 二 Qt Quick 问题交流 1 Qt5 的 QML Settings 没有设置编码的接口 Qt6 虽然移除了 QSettings 的 setIniCodec 接口 默认为 utf8 但是 Qt5 这个
  • FPGA — BRAM 队列实践

    使用软件 Vivado 开发板 EGO1采用Xilinx Artix 7系列XC7A35T 1CSG324C FPGA BRAM 队列实践 功能描述 功能实现 1 添加BRAM的IP 2 数码管显示 3 时钟分频 4 按键消抖 5 顶层设计
  • Java-Exception-异常处理

    一 基本介绍 异常处理就是当异常发生时 对异常处理的方式 二 异常处理的方式 1 try catch finally 程序员在代码中捕获发生的异常 自行处理 2 throws 将发生的异常抛出 交给调用者 方法 来处理 最顶级的处理者就是J
  • 【Android】从SurfaceFlinger中获取各layer图片(4)再回顾

    从SurfaceFlinger中获取各layer图片的试验可以加深对GraphicBuffer和Layer的理解 dumpsys SurfaceFlinger中打印的Slot信息中有GraphicBuffer的指针 可以帮助我们了解Queu
  • thinkphp 用七牛云异步上传文件(前后端代码)

    1 首先创建一个七牛云帐号 完成后 添加对象存储 2 创建成功后 右上角 密钥管理 查看秘钥 找到AK SK 3 打开thinkphp的配置文件 将此代码加入 CONFIG QINIU gt array accessKey gt 你的AK
  • ARouter 使用教程,移动互联网开发工程师

    setContentView R layout activity one 第二步 调用 navigation 方法实现跳转 ARouter getInstance build ARouterConstants COM ACTIVITY1 n
  • whl文件安装库和pip换源

    作者介绍 作者 小刘在C站 每天分享课堂笔记 一起努力 共赴美好人生 夕阳下 是最美的绽放 目录 一 whell介绍 二 whell实现方式 三 whell安装实现 方式一 方式二 步骤1 步骤3 pip的换源 一 pip为什么要换源 二
  • 成功升级scikit-image的版本,从老版本0.13.0到0.17.2

    成功升级scikit image的版本 从老版本0 13 0到0 17 2 之前参考其他博客升级scikit image的版本没有成功 这次参考scikit image的github官网 顺利实现了升级 scikit image的githu
  • 线程和线程的创建

    一 线程的概念 1 实例 系统通过传感器采集数据 并通过显示屏将数据显示出来 在多线程实时系统中 可以将这个任务分解成两个子任务 如上图所示 一个子任务不间断地读取传感器数据 并将数据写到共享内存中 另外一个子任务周期性的从共享内存中读取数
  • [Mysql] 经典 50 题

    50道MySql练习题 本文档只有45道 流传自远古 相当经典 这套练习在多样性和难度上平衡的比较好 换句话说 基础sql查询练习有这套就够了 这套练习在互联网上存在时间悠久 有很多版本 本文档力图在可读性 规范性 可操作性上比这些版本做的
  • 一贴看懂UML,不再发愁看不懂设计模式

    在UML类图中 常见的有以下几种关系 泛化 Generalization 实现 Realization 关联 Association 聚合 Aggregation 组合 Composition 依赖 Dependency 1 泛化 Gene
  • java学习笔记——springmvc 之 @RequestMapping映射与RESTful、请求数据传入 与 响应数据传出、@ModelAttribute 与 视图解析

    一 SpringMVC 概述 1 SpringMVC 概述 Spring 为展现层提供的基于 MVC 设计理念的优秀的 Web 框架 是目前最主流的 MVC 框架之一 Spring3 0 后全面超越 Struts2 成为最优秀的 MVC 框
  • C++中将构造函数或析构函数定义为private

    今天面试被问到了这个单例模式常用到的技术手段 下面进行分析 很多情况下要求当前的程序中只有一个object 例如一个程序只有一个和数据库的连接 只有一个鼠标的object 通常我们都将构造函数的声明置于public区段 假如我们将其放入pr