解析C++中临时对象的产生情况

 更新时间:2023年06月19日 11:01:54   作者:Allen.Su  
临时对象的产生和销毁都是有成本的,都会影响程序的执行性能和效率,所以如果能了解临时对象产生的原因,就可以提升程序的性能和效率,下面小编就来和大家聊聊临时对象产生的几种情况吧

有些临时对象是系统自己产生的,又有一些临时对象却是因为代码的书写问题而产生的,因为临时对象会额外消耗系统资源,所以编写代码的原则就是产生的临时对象越少越好。临时对象一般都是在栈上,所以一般都不会手动去释放。

为什么要了解临时对象?因为临时对象的产生和销毁都是有成本的,都会影响程序的执行性能和效率,所以如果能有效地减少临时对象的产生,那么无疑意味着程序性能和效率的提升。

一、产生临时对象的情况

1.1 以值的方式给函数传递参数 - 如何优化呢

先看如下案例:

class CTempValue
{
public:
    int val1;
    int val2;
public:
    //构造函数
    CTempValue(int v1 = 0, int v2 = 0)
    {
        cout << "调用构造函数" << endl;
        val1 = v1;
        val2 = v2;
        cout << "val1 = " << val1 << endl;
        cout << "val2 = " << val2 << endl;
    }
    ~CTempValue()
    {
        cout << "调用了析构函数" << endl;
    }
    //拷贝构造函数
    CTempValue(const CTempValue& t)
    {
        cout << "调用拷贝构造函数" << endl;
        val1 = t.val1;
        val2 = t.val2;
        cout << "val1 = " << val1 << endl;
        cout << "val2 = " << val2 << endl;
    }
    int Add(CTempValue tobj)
    {
        int tmp = tobj.val1 + tobj.val2;
        tobj.val1 = 1000;   //这里的修改对外界没有影响
        return tmp;
    }
};
​​​​​​​int main()
{
    CTempValue tm(10, 20);  //调用构造函数
    int Sum = tm.Add(tm);   // 这会调用拷贝构造函数的执行
    cout << "Sum = " << Sum << endl;  //Sum = 30
    cout << "tm.val1 = " << tm.val1 << endl; //tm.val1 = 10
    return 0;
}

运行结果如下:

调用构造函数
调用拷贝构造函数
调用了析构函数
Sum = 30
tm.val1 = 10
调用了析构函数

请注意,结果中调用了拷贝构造函数,为什么调用CTempValue类的拷贝构造函数呢?

这是因为调用Add成员函数时把对象tm传递给了Add成员函数,此时,系统会调用拷贝构造函数创建一个副本tobj(成员函数Add的形参),用于在函数体Add内使用,因为tm对象的vall值(tm对象的vall值仍旧为10)。

形参tobj是一个局部对象(局部变量),从程序功能的角度来讲,函数体内需要临时使用它一下,来完成一个程序上的一个功能,它确实是一个局部变量,只能在Add函数体里使用。所以严格意义来讲,它又不能称为一个真正意义的临时对象,因为真正的临时对象往往指的是真实存在,但又感觉不到的对象(至少从代码上不能直接看到的对象)。

但是,对于目前确实生成了tobj对象,并且调用了类的拷贝构造函数,有了复制的动作,就会影响程序的执行效率。

那如何优化呢?

我们可以使用引用:

int Add(CTempValue& tobj){}

1.2 类型转换生成的临时对象 - 如何优化呢

1.2.1 类型转换生成的临时对象

接下来,我们看下真正意义的临时对象,因为这个临时对象确实存在,但是从程序代码的角度不能直接看到它。

CTempValue sum;
sum = 1000;

执行一下,程序结果如下:

调用构造函数
val1 = 0
val2 = 0
调用构造函数
val1 = 1000
val2 = 0
调用了析构函数
调用了析构函数

在执行sum = 1000;系统调用了一次CTempValue类的构造函数和析构函数,这说明系统肯定产生了一个对象,但这个对象在哪里,通过代码完全看不到,所以这个对象是一个真正的临时对象。

那么,产生这个临时对象的原因是什么呢?

是因为把1000赋给sum,而sum本身是一个CTempValue类型的对象,1000是一个数字,那怎么把数字能转化成CTempValue类型的对象呢?所以编译器这里帮助我们以1000为参数调用了CTempValue的构造函数创建了一个临时对象,因为CTempValue构造函数的两个参数都有默认值,所以这里的数字1000就顶替了第一个参数,而第二个参数系统就用了默认值,所以从1000是可以成功创建出CTempValue对象的。

为了进一步观察,增加拷贝赋值运算符的代码:

CTempValue& operator=(const CTempValue& tmpv)
{
    cout << "调用了拷贝赋值运算符" << endl;
    val1 = tmpv.val1;
    val2 = tmpv.val2;
    cout << "val1 = " << val1 << endl;
    cout << "val2 = " << val2 << endl;
    return *this;
}

执行程序,看下结果:

调用构造函数
val1 = 0
val2 = 0
调用构造函数
val1 = 1000
val2 = 0
调用了拷贝赋值运算符
val1 = 1000
val2 = 0
调用了析构函数
调用了析构函数

总结sum = 1000;这行代码系统做了哪些事:

用1000这个数字创建了一个类型为CTempValue的临时对象;调用拷贝赋值运算符把这个临时对象里面的各个成员值赋给了sum对象;销毁这个刚刚创建的CTempValue临时对象。

那如何优化呢?

可以把main主函数中刚刚写的两行代码优化成下面一行:

CTempValue sum = 1000; //" = " 在这里不是赋值运算符,而是定义初始化的概念

运行结果如下:

调用构造函数
val1 = 1000
val2 = 0
调用了析构函数

可以看到,系统没有生成临时对象,所以系统少调用了一次构造函数,少调用了一次拷贝赋值运算符、少调用了一次析构函数。

1.2.2 隐式类型转换以保证函数调用成功

int calc(const string& strsource, char ch)
{
    const char * p = strsource.c_str();
    int icount = 0;
    //......具体的统计代码
    return icount;
}
//在main主函数的代码如下:
int main()
{
    char mystr[100] = "I love China, oh, yeah!";
    int result = calc(mystr, 'o');
    return 0;
}

一个是char数组,一个是const string&,但是这个函数就能调用成功,为什么呢?

当然是编译器帮助我们做了一些事情,解决类型不匹配,那是如何做的呢?

那就是编译器产生了一个类型sring的临时对象,这个临时对象的构造方式就是用mystr作为参数,调用了string的构造函数,这样形参strsoutce就绑定到这个string临时对象上了。当calc函数返回的时候,这个临时对象会被自动销毁。

C++只会为const string&(const引用)产生临时对象,不会为string&(非const)产生临时对象。

1.3 函数返回值的时候 - 如何优化呢

我们在main主函数上面加一个普通的全局函数:

CTempValue Double(CTempValue& ts)
{
    CTempValue tmpm;    //这里会消耗一次构造函数和一次析构函数的调用
    tmpm.val1 = ts.val1 * 2;
    tmpm.val2 = ts.val2 * 2;
    return tmpm; //这里会调用构造函数和析构函数,这表示生成了一个临时对象
}
int main()
{
    CTempValue ts1(10, 20);
    CTempValue ts2 = Double(ts1);  //其实临时对象接管的就是右值
    CTempValue&& ts2 = Double(ts1);  
}

临时对象就是一种右值

那如何优化呢?

CTempValue Double(CTempValue& ts)
{
    return CTempValue(ts.val1 * 2, ts.val2 * 2); 
}

通过如上的优化系统会调用一次拷贝构造函数,一次析构函数。

二、小结

(1)写代码的时候,减少临时变量的产生。

(2)锻炼眼神,能够尽量看出哪些地方可能会产生临时对象,尤其是一个函数只要返回一个对象,一般机会产生临时对象。

到此这篇关于解析C++中临时对象的产生情况的文章就介绍到这了,更多相关C++临时对象内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++中的std::nothrow使用

    C++中的std::nothrow使用

    这篇文章主要介绍了C++中的std::nothrow使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • C++基于蔡基姆拉尔森计算公式实现由年月日确定周几的方法示例

    C++基于蔡基姆拉尔森计算公式实现由年月日确定周几的方法示例

    这篇文章主要介绍了C++基于蔡基姆拉尔森计算公式实现由年月日确定周几的方法,涉及C++针对日期时间的数值运算相关操作技巧,需要的朋友可以参考下
    2017-07-07
  • C++算法系列之日历生成的算法代码

    C++算法系列之日历生成的算法代码

    日历算法首先要知道日历的编排规则,也就是历法。所谓历法,指的就是推算年、月、日的时间长度和它们之间的关系,指定时间序列的法则。
    2018-05-05
  • C++ AnimeGAN实现照片一键动漫化

    C++ AnimeGAN实现照片一键动漫化

    AnimeGAN是是由神经网络风格迁移加生成对抗网络(GAN)而成,它是基于CartoonGAN的改进,并提出了一个更加轻量级的生成器架构。本文将介绍如何运用AnimeGAN实现照片一键动漫化,需要的可以参考一下
    2021-11-11
  • OpenCV c++滑动条的创建和使用代码

    OpenCV c++滑动条的创建和使用代码

    滚动条(Trackbar)在OpenCV中是非常方便的交互工具,它依附于特定的窗口而存在,下面这篇文章主要给大家介绍了关于OpenCV c++滑动条的创建和使用的相关资料,需要的朋友可以参考下
    2023-06-06
  • 纯C语言:分治问题源码分享

    纯C语言:分治问题源码分享

    这篇文章主要介绍了纯C语言:分治问题源码,有需要的朋友可以参考一下
    2014-01-01
  • C++中的继承方式与菱形继承解析

    C++中的继承方式与菱形继承解析

    这篇文章主要介绍了C++中的继承方式与菱形继承解析,继承是类和类之间的关系,是代码复用的重要手段,允许在保持原有类结构的基础上进行扩展,创建的新类与原有的类类似,只是多了几个成员变量和成员函数,需要的朋友可以参考下
    2023-08-08
  • C语言中进制知识汇总

    C语言中进制知识汇总

    在C语言里,整数有三种表示形式:十进制,八进制,十六进制。 其中以数字0开头,由0~7组成的数是八进制。以0X或0x开头,由0~9,A~F或a~f 组成是十六进制。除表示正负的符号外,以1~9开头,由0~9组成是十进制。
    2016-05-05
  • 浅谈C++11中的几种锁

    浅谈C++11中的几种锁

    本文主要介绍了C++11中的几种锁,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • C语言进阶栈帧示例详解教程

    C语言进阶栈帧示例详解教程

    这篇文章主要为大家介绍了C语言进阶栈帧的示例详解教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-02-02

最新评论