按值传递实参到函数和函数返回临时变量的副本,函数的效率对执行性能来说至关重要

如果避免这样的复制操作,则执行时间可能会大大缩短。

class CMessage
{
private:
char * m_pMessage; public:
void showIt()const
{
cout << m_pMessage << endl;
}
//构造函数
CMessage(const char* text = "Default message")
{
cout << " 构造函数" << endl; size_t length{ strlen(text) + };
m_pMessage = new char[length + ];
strcpy_s(m_pMessage, length + , text);
}
//复制构造函数
CMessage(const CMessage & aMess)
{
cout << "复制构造函数" << endl;
size_t len{ strlen(aMess.m_pMessage) + };
this->m_pMessage = new char[len];
strcpy_s(m_pMessage, len, aMess.m_pMessage);
}
//重载赋值运算符
CMessage & operator=(const CMessage & aMess)
{
cout << "重载赋值运算符函数" << endl;
if (this != &aMess)
{
delete[]m_pMessage;
size_t length{ strlen(aMess.m_pMessage) + };
m_pMessage = new char[length];
strcpy_s(this->m_pMessage, length, aMess.m_pMessage);
}
return *this;
} CMessage operator+(const CMessage & aMess)
{
cout <<"重载加法运算符函数" << endl;
size_t len{strlen(m_pMessage)+strlen(aMess.m_pMessage)+};
CMessage message;
message.m_pMessage = new char[len]; strcpy_s(message.m_pMessage,len,m_pMessage);
strcat_s(message.m_pMessage,len,aMess.m_pMessage); return message;
} //析构函数
~CMessage()
{
cout << " 析构函数" << endl;
delete[]m_pMessage;
}
};
int main()
{
CMessage motto1{ "Amiss is " };
CMessage motto2{"as good as a mile"};
CMessage motto3; motto3 = motto1 + motto2; motto3.showIt();
}

运行结果如下:

构造函数            //motto1调用
 构造函数            //motto2调用
 构造函数            //motto3调用
重载加法运算符函数        
 构造函数            //operator+()中message对象调用
复制构造函数           //返回时对message对象的复制,生成message的临时副本
 析构函数            //message调用,销毁临时对象
重载赋值运算符函数        //motto3调用operator=()
 析构函数              //message的临时副本调用
Amiss is as good as a mile
 析构函数
 析构函数
 析构函数

----------------------------------------------------------------------------------------------------------------------------------------------------------------

改进方法:应用rvalue引用形参

当源对象是一个临时对象,在复制操之后立即就被销毁时,复制的替代方案是偷用由 m_pMessage 成员指向的临时对象的内存,并传送到目标对象。

如果这么做,那么不需要为目标对象分配更多的内存,不需要复制对象,也不需要释放源对象拥有的内存。

在操作完成以后将立即销毁源对象,因此这么做没有风险,只是加快了执行速度。

实现此技术的关键是检测复制操作中何时是一个 rvalue。

  CMessage(const CMessage & aMess)
  {
      cout << "复制构造函数" << endl;
      size_t len{ strlen(aMess.m_pMessage) + 1 };
      this->m_pMessage = new char[len];
      strcpy_s(m_pMessage, len, aMess.m_pMessage);
   }

  CMessage(CMessage && aMess)
   {
      cout << "" << endl;
      m_pMessage = aMess.m_pMessage;
      aMess.m_pMessage = nullptr;      //必须要这么做,防止删除原指向的内存
    }

我们知道用对象初始化当前对象、返回临时对象都会调用复制构造函数。

motto3 = motto1 + motto2;调用重载加法运算符函数后会产生临时变量 message。

而对临时变量的复制产生需要的临时副本会增加运行时间。

所以,临时变量的副本可以直接“偷用”源临时变量对象成员指向的内存。通过以上两个函数比较可知,lvalue引用形参多了复制的操作。

难道要 lvalue引用形参的形式没有用了吗?

若: motto3=motto1 时,可知必须用 lvalue 引用形参的形式。如果 rvalue 可用,将会使两个对象同时指向一块内存。

所以, rvalue 针对 临时变量。

可以像下面这样额外创建 operator=()函数的重载

  CMessage & operator=(CMessage && aMess)
   {
      cout <<"Move assignment operator function called." << endl;
      delete[]m_pMessage;
      m_pMessage = aMess.m_pMessage;
      aMess.m_pMessage = nullptr;       //必须要这么做,防止删除原指向的内存
      return *this;
   }

  CMessage & operator=(const CMessage & aMess)
  {
     cout << "重载赋值运算符函数" << endl;
     if (this != &aMess)
     {   delete[]m_pMessage;
        size_t length{ strlen(aMess.m_pMessage) + 1 };
          m_pMessage = new char[length];
        strcpy_s(this->m_pMessage, length, aMess.m_pMessage);
     }
     return *this;
  }

观察这两个 lvalue、rvalue引用形参重载赋值运算符函数的区别:

motto3 = motto1 + motto2;调用重载加法运算符函数后会产生临时变量 message。在函数返回时又调用 rvalue引用形参类型的复制构造函数,

产生临时对象 message 的临时副本。

之后调用重载赋值运算符函数,由于此时仍是临时对象的副本,

所以,仍可以采用“ 偷换 ”源临时变量对象成员指向的内存。而避免赋值函数对对象成员的复制。

临时对象由编译器生成,使用之后会自动调用析构函数释放。

所以此处需要我们通过观察代码运行,自己来理解。

  CMessage operator+(const CMessage & aMess)
   {
    cout <<"重载加法运算符函数" << endl;
    size_t len{strlen(m_pMessage)+strlen(aMess.m_pMessage)+1};
    CMessage message;
    message.m_pMessage = new char[len];

   strcpy_s(message.m_pMessage,len,m_pMessage);
    strcat_s(message.m_pMessage,len,aMess.m_pMessage);

  return message;
   }

不知道你有没想过哟,为什么上面函数没有返回引用,引用可以避免不必要的复制,不是很方便吗?

添加引用 CMessage operator+(const CMessage & aMess)

运行结果:

   构造函数
   构造函数
   构造函数
  重载加法运算符函数
   构造函数
   析构函数
  重载赋值运算符函数
请按任意键继续. . .

发现程序崩溃,运行到重载赋值运算符函数就不能继续运行了。

Why?

如果被返回的对象是被调用函数中的局部变量,则不应按引用方式返回它。

因为,在被调用函数执行完毕时,局部对象将调用其 析构函数。

如果函数返回一个没有公有复制构造函数的类(如 ostream 类)的对象,它必须返回指向对象的引用。

如果在类中定义了 operator=()成员函数和复制构造函数时,将形参定义为非常量 rvalue 引用,则需要确保也定义了具有 const lvalue引用形参的标准版本。

编译器会提供它们的默认版本,逐一成员的进行复制。

对象复制问题 && lvalue-rvalue && 引用的更多相关文章

  1. OC_内存管理(二)对象复制、循环引用问题、自动释放池

      循环调用: 1.循环引用的问题 两个对象A.B,有可能会出现特殊情况:A中包含B的实例变量:B中也包含A的实例变量,如果这两个实例变量都是强引用(A有着B的实例变量所有权,B也有A的实例变量所有权 ...

  2. Java学习笔记----你可能不知道那些知识,对象复制与引用

    1.private ,protected,static不能用来修饰interface. 2.java在处理基本数据类型(比如int ,char,double)时,都是採用按值传递的方式运行.除此之外的 ...

  3. JS对象复制

    在JavaScript很多人复制一个对象的时候都是直接用"=",因为大家都觉得脚本语言是没有指针.引用.地址之类的,所以直接用"="就可以把一个对象复制给另外一 ...

  4. PHP写时复制, 变量复制和对象复制不同!!!

    2016年3月18日 15:09:28 星期五 一直以为PHP对象也是写时复制....... 其实: PHP的变量是写时复制, 对象是引用的 写时复制: $a = $b; 如果$b的内容不改变, $a ...

  5. 对象复制、克隆、深度clone

    -------------------------------------------------------------------------------- ------------------- ...

  6. JS对象复制(深拷贝、浅拷贝)

    如何在 JS 中复制对象 在本文中,我们将从浅拷贝(shallow copy)和深拷贝(deep copy)两个方面,介绍多种 JS 中复制对象的方法. 在开始之前,有一些基础知识值得一提:Javas ...

  7. JavaScript对象复制(一)(转载)

    在JavaScript很多人复制一个对象的时候都是直接用"=",因为大家都觉得脚本语言是没有指针.引用.地址之类的,所以直接用"="就可以把一个对象复制给另外一 ...

  8. [No0000B9]C# 类型基础 值类型和引用类型 及其 对象复制 浅度复制vs深度复制 深入研究2

    接上[No0000B5]C# 类型基础 值类型和引用类型 及其 对象判等 深入研究1 对象复制 有的时候,创建一个对象可能会非常耗时,比如对象需要从远程数据库中获取数据来填充,又或者创建对象需要读取硬 ...

  9. 关于delete和对象复制

    本码农的惯例,开篇废话几句... 前天小生又被虐了... 没办法,作为一个资深code user,我用代码的能力,解决问题的能力自问是不弱的... 但是自身的前端基础说实话还是不过硬,最明显的表现就是 ...

随机推荐

  1. JavaScript中经典方法

    jQuery()通过name名称获取当前name中value数组 /** 获取input中name属性相同的 value数组 */ function my_array_name(m){ var val ...

  2. Qt Style Sheet实践(二):组合框QComboBox的定制

    导读 组合框是一个重要且应用广泛的组件,一般由两个子组件组成:文本下拉单部分和按钮部分.在许多既需要用户选择.又需要用户手动输入的应用场景下,组合框能够很好的满足我们的需求.如我们经常使用的聊天软件Q ...

  3. [水煮 ASP.NET Web API2 方法论](3-5)路由约束

    问题 怎么样限制路由中参数的值. 解决方案 ASP.NET WEB API 允许我们通过 IHttpRouteConstraint 接口设置路由约束.集中式路由和直接式路由都可以使用 IHttpRou ...

  4. Oracle客户端+PLSQLDeveloper实现远程登录Oracle数据库

    Oracle数据库功能强大.性能卓越,在造就这些优点的同时,也导致Oracle占内存比较多.针对这个问题,我们如何做到取其精华去其糟粕呢? 解决方案:我们可以在局域网内的服务器上安装庞大的Oracle ...

  5. git 给远程库 添加多个url地址

     目录[-] 前提 使用流程 原理解析 注意 Other 参考文章 作者:shede333主页:http://my.oschina.net/shede333 && http://blo ...

  6. [转载]hashmap hashtable 的区别

    Hashtable 和 HashMap 做为 Map 的基本特性 两者都实现了Map接口,基本特性相同 -          对同一个Key,只会有一个对应的value值存在 -          如 ...

  7. spring mvc绑定复杂对象报错“Could not instantiate property type [com.ld.net.spider.pojo.WorkNode] to auto-grow nested property path: java.lang.InstantiationException: com.ld.net.spider.pojo.WorkNode”

    解决方法之一: 1.确保所有的Pojo都包含了默认的构造器:

  8. PHP调用SQL Server存储过程

    一.安装SQL Server Driver for PHP     在微软官网上发现了这个东西,他提供了一套PHP对MS2005/2008操作的全新函数库,并且支持UTF8,作为PHP的扩展运行.看来 ...

  9. 项目、SVN clean的一些事

    1.如果你发现你的文件修改了.Tomcat也重新了,但访问的还是旧的文件,这个时候你需要clean下你的项目. Clean will discard all build problems and bu ...

  10. python初识(2)

    1.关于编码转换的方式. 比如,讲utf-8的编码转换为unicode方式如下 #-*- coding:utf-8 -*- i="德玛西亚" i_unicode=i_decode( ...