技术标签: c++ Meyers98 工作 delete class object
条款31: 千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用
本条款听起来很复杂,其实不然。它只是一个很简单的道理,真的,相信我。
先看第一种情况:返回一个局部对象的引用。它的问题在于,局部对象 ----- 顾名思义 ---- 仅仅是局部的。也就是说,局部对象是在被定义时创建,在离开生命空间时被销毁的。所谓生命空间,是指它们所在的函数体。当函数返回时,程序的控制离开了这个空间,所以函数内部所有的局部对象被自动销毁。因此,如果返回局部对象的引用,那个局部对象其实已经在函数调用者使用它之前被销毁了。
当想提高程序的效率而使函数的结果通过引用而不是值返回时,这个问题就会出现。下面的例子和条款23中的一样,其目的在于详细说明什么时候该返回引用,什么时候不该:
class Rational { // 一个有理数类
public:
Rational(int numerator = 0, int denominator = 1);
~Rational();
...
private:
int n, d; // 分子和分母
// 注意operator* (不正确地)返回了一个引用
friend const Rational& operator*(const Rational& lhs,
const Rational& rhs);
};
// operator*不正确的实现
inline const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
这里,局部对象result在刚进入operator*函数体时就被创建。但是,所有的局部对象在离开它们所在的空间时都要被自动销毁。具体到这个例子来说,result是在执行return语句后离开它所在的空间的。所以,如果这样写:
Rational two = 2;
Rational four = two * two; // 同operator*(two, two)
函数调用时将发生如下事件:
1. 局部对象result被创建。
2. 初始化一个引用,使之成为result的另一个名字;这个引用先放在另一边,留做operator*的返回值。
3. 局部对象result被销毁,它在堆栈所占的空间可被本程序其它部分或其他程序使用。
4. 用步骤2中的引用初始化对象four。
一切都很正常,直到第4步才产生了错误,借用高科技界的话来说,产生了"一个巨大的错误"。因为,第2步被初始化的引用在第3步结束时指向的不再是一个有效的对象,所以对象four的初始化结果完全是不可确定的。
教训很明显:别返回一个局部对象的引用。
"那好,"你可能会说,"问题不就在于要使用的对象离开它所在的空间太早吗?我能解决。不要使用局部对象,可以用new来解决这个问题。"象下面这样:
// operator*的另一个不正确的实现
inline const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
// create a new object on the heap
Rational *result =
new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
// return it
return *result;
}
这个方法的确避免了上面例子中的问题,但却引发了新的难题。大家都知道,为了在程序中避免内存泄漏,就必须确保对每个用new产生的指针调用delete,但是,这里的问题是,对于这个函数中使用的new,谁来进行对应的delete调用呢?
显然,operator*的调用者应该负责调用delete。真的显然吗?遗憾的是,即使你白纸黑字将它写成规定,也无法解决问题。之所以做出这么悲观的判断,是基于两条理由:
第一,大家都知道,程序员这类人是很马虎的。这不是指你马虎或我马虎,而是指,没有哪个程序员不和某个有这类习性的人打交道。想让这样的程序员记住无论何时调用operator*后必须得到结果的指针然后调用delete,这样的几率有多大呢?也是说,他们必须这样使用operator*:
const Rational& four = two * two; // 得到废弃的指针;
// 将它存在一个引用中
...
delete &four; // 得到指针并删除
这样的几率将会小得不能再小。记住,只要有哪怕一个operator*的调用者忘了这条规则,就会造成内存泄漏。
返回废弃的指针还有另外一个更严重的问题,即使是最尽责的程序员也难以避免。因为常常有这种情况,operator*的结果只是临时用于中间值,它的存在只是为了计算一个更大的表达式。例如:
Rational one(1), two(2), three(3), four(4);
Rational product;
product = one * two * three * four;
product的计算表达式需要三个单独的operator*调用,以相应的函数形式重写这个表达式会看得更清楚:
product = operator*(operator*(operator*(one, two), three), four);
是的,每个operator*调用所返回的对象都要被删除,但在这里无法调用delete,因为没有哪个返回对象被保存下来。
解决这一难题的唯一方案是叫用户这样写代码:
const Rational& temp1 = one * two;
const Rational& temp2 = temp1 * three;
const Rational& temp3 = temp2 * four;
delete &temp1;
delete &temp2;
delete &temp3;
果真如此的话,你所能期待的最好结果是人们将不再理睬你。更现实一点,你将会在指责声中度日,或者可能会被判处10年苦力去写威化饼干机或烤面包机的微代码。
所以要记住你的教训:写一个返回废弃指针的函数无异于坐等内存泄漏的来临。
另外,假如你认为自己想出了什么办法可以避免"返回局部对象的引用"所带来的不确定行为,以及"返回堆(heap)上分配的对象的引用"所带来的内存泄漏,那么,请转到条款23,看看为什么返回局部静态(static)对象的引用也会工作不正常。看了之后,也许会帮助你避免头痛医脚所带来的麻烦。
文章浏览阅读1.7w次,点赞6次,收藏118次。Java 面试随着时间的改变而改变。在过去的日子里,当你知道 String 和 StringBuilder 的区别就能让你直接进入第二轮面试,但是现在问题变得越来越高级,面试官问的问题也更深入。 在我初入职场的时候,类似于 Vector 与 Array 的区别、HashMap 与 Hashtable 的区别是最流行的问题,只需要记住它们,就能在面试中获得更好的机会,但这种情形已经不复存在。如今,你...
文章浏览阅读240次。CNN基础知识卷积神经网络(Convolutional Neural Network, CNN)是一种结构类似于人类或动物的 视觉系统 的人工神经网络,包含一个或多个卷积层(Convolutional Layer)、池化层(Pooling Layer)和全连接层(Fully-connected Layer)。示例代码# -*- coding:utf-8 -*-# /usr/bin/p..._cnn_class
文章浏览阅读2.9k次,点赞4次,收藏21次。用栈实现:输入一行符号,以#结束,判断其中的括号是否匹配。括号包括:{ } 、 [ ] 、 ( )、 < >如果匹配,输出 right如果不匹配,给出错误提示。包括: (1)对称符号都匹配,输出 “ right “ (2)处理到某个符号时不匹配了,输出 " The $ character '*' is wrong." ,其中$是出错符号的序号,*是出错..._6-4 括号匹配 分数 15 作者 张瑞霞 单位 桂林电子科技大学 本题要求通过栈来判断
该题目是要求将一个3×3矩阵进行转置,即行和列互换。输入格式为一行9个小于100的整数,输出格式为3行3列的二维数组,每个数据输出占4列。例子给出了一个输入样例和输出样例。使用for循环和格式化输出即可实现。
文章浏览阅读1.2w次,点赞3次,收藏65次。卷积神经网络(Convolutional Neural Networks, CNN)是一类包含卷积计算且具有深度结构的前馈神经网络(Feedforward Neural Networks),是深度学习(deep learning)的代表算法之一。顾名思义,就是将卷积与前馈神经网络结合,所衍生出来的一种深度学习算法。卷积神经网络CNN的结构图使用卷积神经网络(CNN)实现猫狗分类是一种有效的方法,它能够自动从图像中学习特征并进行分类,提高准确性。_cnn猫狗分类
文章浏览阅读2.9k次。Inserts a placeholder for a tensor that will be always fed.tf.compat.v1.placeholder( dtype, shape=None, name=None)Important: This tensor will produce an error if evaluated. Its valu...
文章浏览阅读522次。例如:给定 N=6 以及 A[0]=20 A[1]=10 A[2]=30 A[3]=30 A[4]=40 A[5]=10 此函数应返回 10 或者 30。如果字符串A和字符串B含有相同的字母,但是顺序可能不一样,那么A被称为是B的anagram。对于数组中下标为 k 的元素,A[k] 包含了列表中下一个节点的下标,或者是 -1 ——表示列表到此结束,没有下一个节点了。从给出的非空整数数组 A(下标从 0 开始算)中,找到那个藏在其中的列表的长度,将长度值返回。5. Sleep()和wait()的区别?..._亚信科技笔试题目
文章浏览阅读757次。系统架构图属于系统设计阶段,系统架构图只是这个阶段一个产物,系统的总体架构决定了整个系统的模式,是系统的基础。springboot基于android的天干地支文化科普和动画系统。springboot基于微信小程序的企业资源预约系统的设计与实现。springboot基于springboot的母婴服务管理系统。springboot基于Android的天气预报及推荐系统。springboot基于springboot的外卖系统。ssm基于SSM的儿童疫苗接种管理系统的设计与实现。
文章浏览阅读686次,点赞20次,收藏11次。当前的社交推荐模型通常关注建模多图结构,并从这些多图中汇集信息以学习用户的偏好。然而,这些方法往往使用复杂的模型和冗余的参数来获得轻微的性能改进。因此,本文提出了一种集成社交图和交互图信息的对比学习方法,通过对用户嵌入进行融合来获取更精细的用户表示。同时,引入了对比学习框架,通过数据增强构建对比学习的正负样本。总体而言,这篇论文介绍了一种基于对比学习的图卷积网络方法,用于改进社交推荐系统的性能。通过融合社交图和交互图信息,并利用对比学习框架进行数据增强,该方法能够更好地学习用户的偏好。_cross-view temporal graph contrastive learning for session-based recommendat
文章浏览阅读5.7k次,点赞19次,收藏69次。Quartus使用记录及与modelsim联合仿真_quartus仿真
文章浏览阅读3.6k次,点赞2次,收藏3次。编写函数 int fun(int n)判断n是否是闰年,定义main函数输入年份,调用fun函数判断,在main函数中输出yes或no。请只提供子函数的编写。【样例输入】2000【样例输出】yesint fun(int n)_本关任务:编写一个函数,能判断年份n是否是闰年,并调用该函数判断输入的年份是否为
文章浏览阅读1.2k次。1、Throws 如果在当前方法不知道该如何处理该异常时,则可以使用throws对异常进行抛出给调用者处理或者交给JVM。调用者调用此方法,要么抛出要么try catch处理,到了JVM这里,就是打印出异常堆栈,并终止运行。换句话说,用这个有两种情况。 1>我throws抛出异常,如果是检查异常,那么调用者必须捕获或再次抛出 2>我th..._throws必须捕获或者throw吗