c++ 临时对象的来源

首先看下面一端代码:


代码如下:

#include <iostream>
 void swap( int &a,int &b)
 {
     int temp;
     temp=a;
     a=b;
     b=temp;
 }

int main(int argc,char** argv)
 {
     int a=1,b=2;
     swap(a,b);
     std::cout<<a<<"-----"<<b<<std::endl;
     return 0;
 }

结果为
 2-----1  
 可能大多数园友,认为"int temp"是"临时对象",但是其实不然,"int temp"仅仅是swap函数的局部变量。

临时对象是代码中看不到的,但是实际程序中确实存在的对象。临时对象是可以被编译器感知的。

为什么研究临时对象?
主要是为了提高程序的性能以及效率,因为临时对象的构造与析构对系统开销也是不小的,所以我们应该去了解它们,知道它们如何造成,从而尽可能去避免它们。

临时对象建立一个没有命名的非堆对象会产生临时对象。(不了解什么是堆对象和非堆对象,可以参考C++你最好不要做的这一博文,这里面有介绍。)这种未命名的对象通常在三种条件下产生:为了使函数成功调用而进行隐式类型转换时候、传递函数参数和函数返回对象时候。

那么首先看看为了使函数成功调用而进行隐式类型转换。


代码如下:

#include <iostream>
  int countChar(const std::string & s,const char c)
  {
      int count=0;
      for(int i=0;i<s.length( );i++)
      {
          if(*(s.c_str( )+i) == c)
          {
              count++;
         }
    }
     return count;
 }

int main(int argc,char** argv)
 {
     char buffer[200];
     char c;
     std::cout<<"please input the string:";
     std::cin>>buffer;
     std::cout<<"please input the char which you want to chount:";
     std::cin>>c;
     int count=countChar(buffer,c);
     std::count<<"the count is:"<<count<<std::endl;
     return 0;
 }

结果为:

这里调用函数countChar(const std::string& s,const char& c),那么我们看看这个函数的形参是const std::string &s,形参类型为const std::string,但是实际上传递的是char buffer[200]这个数组。其实这里编译器为了使函数调用成功做了类型转换,char *类型转换为了std::string类型,这个转换是通过一个赋值构造函数进行的,以buffer做为参数构建一个std::string类型的临时对象。当constChar返回时,即函数撤销,那么这个std::string临时对象也就释放了。但是其实从整个程序上来说临时对象的构造与释放是不必要的开销,我们可以提高代码的效率修改一下代码避免无所谓的转换。所以知道临时对象的来源,可以对程序性能上有一个小小提升。

注意仅当通过传值方式传递对象或者传递常量引用参数,才会发生这类型的转换,当传递非常量引用的参数对象就不会发生。因为传递非常量的引用参数的意图就是想通过函数来改变其传递参数的值,但是函数其实是改变的类型转换建立的临时对象,所以意图无法实现,编译器干脆就直接拒绝。

第二种情况是大家熟悉的函数传递参数的时候,会构造对应的临时对象。看下面一段代码运行的结果想必就一清二楚了。


代码如下:

#include<iostream>
 class People
 {
     public:
         People(std::string n,int a)
         :name(n),age(a)
         {
             std::count<<"h2"<<std::endl;
         }
        People( )
        {
            std::count<<"h1"<<std::endl;
        }
        People(const People& P)
        {
            name=p.name;
            age=p.age;
            std::cout<<"h3"<<std::endl;
        }
        std::string name;
        int age;
};

void swap(People p1,People p2)
{
    People temp;
    temp.age=p1.age;
    temp.name=p1.name;
    p1.age=p2.age;
    p1.name=p2.name;
    p2.age=temp.age;
    p2.name=temp.name;
}

int main(int argc, char ** argv)
{
    People p1("tom",18),p2("sam",19);
    swap(p1,p2);
    return 0;
}

结果为:

这里分析下前面两个"h2"是通过调用构造函数People(std::string n,int a)打印出来的,而"h3"就是通过调用复制构造函数People(const People&)而建立临时对象打印出来的,h1是调用默认构造函数People( )打印出来的。那么怎么避免临时对象的建立呢?很简单,我们通过引用实参而达到目的

void swap(People &p1,People &p2)
第三种情景就是函数返回对象时候。这里要注意临时对象的创建是通过复制构造函数构造出来的。

例如   const Rationanl operator+(Rationanl a,Rationanl b)该函数的返回值的临时的,因为它没有被命名,它只是函数的返回值。每回必须为调用add构造和释放这个对象而付出代价。


代码如下:

#include <iostream>
 class Rationanl
 {
     public:
         Rationanl(int e,int d)
         :_elemem(e),_denom(d)
         {
             std::cout<<"h2"<<std::endl;
         }
         void show( ) const;
         int elemem() const {return _elemem;}
         int denom() const {return _denom;}
         void  setElemon(int e){_elemon=e;}
         void  setDenom(int d) {_denom=d;}
         Rationanl(const Rationanl &r);
         Rationanl & operator=(const Rationanl &r);
     private:
         int _elemem;
         int _denom;
 };
 Rationanl::Rationanl(const Rationanl &r)
 {
     setElemon(r.elemon( ));
     setDenom(r.denom( ) );
     std::cout<<"h3"<<std::endl;
 }
 Rationanl & Rationanl::operator=(const Rationanl &r)
 {
     setElemon(r.elemon( ));
     setDenom(r.denom( ) );
     std::cout<<"h4"<<std::endl;
     return *this;
 }

void Rationanl::show( )
 {
         std::cout<<_elemen<<"/"<<_denom<<std::endl;
 }
 const Rationanl operator*(const Rationanl lhs,const Rationanl rhs)
 {
     return Rational result(lhs.elemen*rhs.elemen,rhs.denom*rhs.denom);
 }

int main(int argc,char **argv)
 {
     Rationanl r1(1,2),r2(1,3)
     Rationanl r3=r1*r2;    //GCC做了优化,没有看到临时变量。编译器直接跳过建立r3,使用赋值符号
     r3.show( );
     //相当于  (r1*r2).show( );
     return 0;
 }

结果为:

这里很可惜没有看到我们想到看到的结果,结果应该为h2,h2,h2,h3,h4,应该是在返回值的时候有一个赋值构造函数,建立临时变量的,后来经笔者网上查找资料证实GCC做了优化。

(0)

相关推荐

  • C++临时性对象的生命周期详细解析

    有关临时对象的生命周期有三种情况: 1)一般情况:临时性对象的被摧毁,应该是对完整表达式(full-expression)求值过程中的最后一个步骤.该完整表达式造成临时对象的产生. 实例代码如下: 复制代码 代码如下: #include <iostream>using namespace std;class A{public:    A(int i): m_i(i)    {        cout << "A(): " << m_i <<

  • c++ 临时对象的来源

    首先看下面一端代码: 复制代码 代码如下: #include <iostream> void swap( int &a,int &b) {     int temp;     temp=a;     a=b;     b=temp; } int main(int argc,char** argv) {     int a=1,b=2;     swap(a,b);     std::cout<<a<<"-----"<<b&

  • 深入c++中临时对象的析构时机的详解

    c++中,临时对象一旦不需要,就会调用析构函数,释放其占有的资源:而具名对象则是与创建的顺序相反,依次调用析构函数. c++源码: 复制代码 代码如下: class X  {public:   int i;   int j;   ~X() {}   X() {} }; int main() {    X x1;    X();    x1.i = 1;    X x2; } 对应的汇编码: 复制代码 代码如下: _main    PROC ; 11   : int main() { push  

  • 7分钟读懂Go的临时对象池pool以及其应用场景

    临时对象池 pool 是啥? sync.Pool 给了一大段注释来说明 pool 是啥,我们看看这段都说了些什么. 临时对象池是一些可以分别存储和取出的临时对象. 池中的对象会在没有任何通知的情况下被移出(释放或者重新取出使用).如果 pool 中持有某个对象的唯一引用,则该对象很可能会被回收. Pool 在多 goroutine 使用环境中是安全的. Pool 是用来缓存已经申请了的 目前未使用的 接下来可能会使用的 内存,以此缓解 GC 压力.使用它可以方便高效的构建线程安全的 free l

  • 详解C++ 临时量与临时对象及程序的相关优化

    一.临时量与临时对象 临时量: 内置类型生成的临时量是常量(临时量,寄存器带出来). 自定义类型生成的临时量是变量 ,在内存中. 隐式生成生成的临时量是常量 ,显式生成生成的临时量是变量 . 临时对象: 临时对象是系统临时分配的对象,在没主动声明所需对象而又使用其功能时产生的 显示对象:出现类型名 隐式对象:不出现类型名 注意: 临时对象的生存周期只在本条语句,临时对象一旦被引用,它的生存周期就和引用相同. 对象如何生成? 先分配内存 在调用构造函数初始化对象的成员变量  产生对象对象析构了 对

  • 详解C++ 中的临时对象

    C++中临时对象(Temporary Object)又称无名对象.临时对象主要出现在如下场景. 1.建立一个没有命名的非堆(non-heap)对象,也就是无名对象时,会产生临时对象. Integer inte= Integer(5); //用无名临时对象初始化一个对象 2.构造函数作为隐式类型转换函数时,会创建临时对象,用作实参传递给函数. 例: class Integer { public: Integer(int i):m_val(i){} ~Integer(){} private: int

  • C++中临时对象的常见产生情况及其解决的方案

    目录 前言 1. 以值传递的方式给函数传参 验证临时对象的而外开销(1) 解决方案 2. 类型转换成临时对象 / 隐式类型转换保证函数调用成功 验证临时对象的而外开销(2) 解决方案 3. 函数返回对象时候 验证临时对象的而外开销(3) 解决方案 前言 在C++中很容易就写出一些代码,这些代码的特点就是偷偷的给你产生了一些临时对象,导致临时对象会调用拷贝构造函数,赋值运算符,析构函数,假如该对象还有继承的话,也会调用父类的拷贝构造函数,赋值运算赋函数等.这些临时对象所调用的函数,都是不必要的开销

  • C++简明分析临时对象是什么

    目录 一.初探临时对象 1.问题 2.思考 3.答案 二.编译器的行为 三.小结 一.初探临时对象 1.问题 下面的程序输出什么?为什么? 下面编写程序进行实验: #include <stdio.h> class Test { int mi; public: Test(int i) { mi = i; } Test() { Test(0); } void print() { printf("mi = %d\n", mi); } }; int main() { Test t;

  • 基于JavaScript将表单序列化类型的数据转化成对象的处理(允许对象中包含对象)

    表单序列化类型的数据是指url传递的数据的格式,形如"key=value&key=value&key=value"这样的key/value的键值对.一般来说使用jQuery的$.fn.serialize函数能达到这样的效果.如何将这样的格式转化为对象? 我们知道使用jQuery的$.fn.serializeArray函数得到的是一个如下结构的对象 [ { name: "startTime" value: "2015-12-02 00:00:

  • JavaScript包装对象使用介绍

    JavaScript是面向对象的语言,使用"."操作符可以访问对象的属性和方法,而对于基本类型(null, undefined, bool, number, string)应该是值类型,没有属性和方法,然而 复制代码 代码如下: var s='this is a string';alert(s.length);alert(s.indexOf('is')); 结果很简单,但是仔细想想还真奇怪,string不是值类型吗!怎么又有属性又有方法的! 内置对象 JavaScript有一系列内置对

  • JavaScript中的包装对象介绍

    javascript对象是一种复合值,它是属性或已命名的值的集合,通过符号"."来引用属性值,当属性值是一个函数的时候,我们称之为方法.我们看到字符串也具有属性和方法: 复制代码 代码如下: var s="hello,world!"; var word=s.substring(s.indexof("")+1,s.length); 字符串既然不是对象,为什么它又有属性呢,只要引用了字符串s的属性,javascript就会将字符串值通过调用new S

随机推荐