C++中临时对象的产生与优化

C++中临时对象的产生与优化

本文主要介绍c++中临时对象产生的几种情况,同时介绍避免的策略。由于在C++中对象的创建和消除会调用该对象对应的构造和析构函数,是一个相对比较耗时的操作,从程序效率角度来说我们应该了解并消除临时对象;又如果类中有指针,更应该了解临时对象,避免指针悬挂和内存泄露等问题。

一、拷贝构造函数和赋值操作符

在深入讨论临时对象之前来看一下拷贝构造函数和赋值操作符,为什么呢?一般而言,临时对象的产生一般对象的赋值或者拷贝,这时就需要了解拷贝操作和赋值操作。

1.        拷贝操作:对象在定义时进行的初始化操作,此时调用的是拷贝构造函数完成。

2.        赋值操作:对象定以后的赋值操作,此时调用的是赋值操作符完成。

那么什么时候进行拷贝操作,什么时候进行赋值操作呢?如定义所示一个变量在第一次定义时进行的操作一般为拷贝操作,例如 A a; A a2 = 1; A a3(a2); A a4 = a3; 进行赋值操作的前提是变量左值已经定义,例如A a; A b; b=a;

// 验证拷贝构造函数 和 赋值操作符
#include <iostream>
 
class TestObject {
 public:
  TestObject() {
    std::cout << "TestObject() is called:"
      << con_call_num_++<< std::endl;
  }
  TestObject(int _x) {
    x_ = _x;
    std::cout << "TestObject(int) is called:"
      << con_call_num_++<< std::endl;
  }
  TestObject (TestObject & to) {
    x_ = to.x_;
    std::cout << "TestObject(TestObject) is called:"
      << ccon_call_num_++ << std::endl;
  }
  TestObject (const TestObject& to) {
    x_ = to.x_;
    std::cout << "TestObject(const TestObject) is called:"
      << ccon_call_num_++ << std::endl;
  }
  TestObject &operator =(const TestObject& to) {
    if (&to == this)
      return *this;
    x_ = to.x_;
    std::cout << "operator =(const) is called:"
      << equal_call_num_++ << std::endl;
    return *this;
  }
  TestObject &operator =(TestObject& to) {
    if (&to == this)
      return *this;
    x_ = to.x_;
    std::cout << "operator =() is called:"
      << equal_call_num_++ << std::endl;
    return *this;
  }
  ~TestObject() {
    std::cout << "~TestObject() is called:"
      << des_call_num_++ << std::endl;
  }
 private:
  int x_;
  static int con_call_num_;
  static int ccon_call_num_;
  static int equal_call_num_;
  static int des_call_num_;
};
 
int TestObject::con_call_num_ = 0;
int TestObject::ccon_call_num_ = 0;
int TestObject::equal_call_num_ = 0;
int TestObject::des_call_num_ = 0;
 
int main() {
  // copy
  TestObject to_1;
  TestObject to_2(1);
  TestObject to_3 = 1;
  TestObject to_4 = to_2;
  TestObject to_5(to_4);
  // assign
  to_1 = to_2;
  return 0;
}

其对应的输出为:

TestObject() is called:0
TestObject(int) is called:1
TestObject(int) is called:2
TestObject(TestObject) is called:0
TestObject(TestObject) is called:1
operator =() is called:0
~TestObject() is called:0
~TestObject() is called:1
~TestObject() is called:2
~TestObject() is called:3
~TestObject() is called:4

 从输出中可以看出带有”=“进行的不一定是赋值操作符,也有可能是拷贝构造函数。另外解决对象的深浅拷贝问题,要同时注意重载拷贝构造函数和赋值操作符。记住默认的拷贝和赋值操作均为浅拷贝。

二、临时对象的产生与优化

总体来说,临时对象产生主要有以下三种情况,

1.        以值的方式给函数传参

2.        隐式类型转换

3.        函数返回一个对象时

2.1 以值的方式给函数传参

临时对象产生点在于给函数参数传值时,先生成该对象的临时对象,调用拷贝构造函数进行拷贝。消除方法是尽量使用引用或者传指针的方式。

void Fun1(TestObject to) {
  std::cout << "Fun1(TestObject) is called" << std::endl;
}
void Fun1_1(TestObject & to) {
  std::cout << "Fun1_1(TestObject&) is called" << std::endl;
}
int main() {
  TestObject to_2;                                                                              
  std::cout << "-----Test type 1-------" << std::endl;
  Fun1(to_2);
  Fun1_1(to_2);
  return 0;
}

产生输出如下:

TestObject() is called:0

-----Test type 1-------
TestObject(TestObject) is called:0
Fun1(TestObject) is called
~TestObject() is called:0
Fun1_1(TestObject&) is called
~TestObject() is called:1

说明:在函数传入参数时调用的是拷贝构造函数,并且临时对象的释放在一个完整语句结束时释放,即第一个分号之后。

2.2 隐式类型转换

临时对象的产生点在于发送隐式类型转换时,先进行类型转换,在进行赋值。消除方法是尽量定义即进行初始化。

int main() {
  TestObject to_2;
  std::cout << "-----Test type 1-------" << std::endl;
  Fun1(to_2);
  Fun1_1(to_2);
  std::cout << "-----Test type 2-------" << std::endl;
  TestObject to_3;
  to_3 = 1;
  TestObject to_4 = 1;                                                                          
  return 0;
}

输出结果为:

-----Test type 1-------
TestObject(TestObject) is called:0
Fun1(TestObject) is called
~TestObject() is called:0
Fun1_1(TestObject&) is called
-----Test type 2-------
TestObject() is called:1
TestObject(int) is called:2
operator =(const) is called:0
~TestObject() is called:1
TestObject(int) is called:3
~TestObject() is called:2
~TestObject() is called:3
~TestObject() is called:4

在进行隐式类型转换进行的操作为:先调用构造函数生成临时对象,在进行赋值操作。

2.3函数返回一个对象时

临时对象产生点在于返回对象时先生成一个临时对象,在进行拷贝操作,消除方法是尽量不要返回对象,如果需要返回,可以考虑进行传入指针的方式进行赋值。

TestObject Fun2_1() {
  std::cout << "Fun2_1() is called" << std::endl;
  return 1;
}
TestObject Fun2_2() {
  std::cout << "Fun2_2() is called" << std::endl;
  TestObject to;
  to.set_x(1);
  return to;
}
int main() {
  /*
  TestObject to_2;
  std::cout << "-----Test type 1-------" << std::endl;
  Fun1(to_2);
  Fun1_1(to_2);
  std::cout << "-----Test type 2-------" << std::endl;
  TestObject to_3;
  to_3 = 1;
  TestObject to_4 = 1;
  */
  std::cout << "-----Test type 3-------" << std::endl;
  Fun2_1();
  Fun2_2();
  return 0;
} 

输出结果为:

-----Test type 3-------
Fun2_1() is called
TestObject(int) is called:0
~TestObject() is called:0
Fun2_2() is called
TestObject() is called:1
~TestObject() is called:1

可以看到没有调用拷贝构造函数,主要因为现在编译器比较智能了能够对类似情况进行优化。

C++中的返回值优化(returnvalue optimization)

返回值优化(Return ValueOptimization,简称RVO),是这么一种优化机制:当函数需要返回一个对象的时候,如果自己创建一个临时对象用户返回,那么这个临时对象会消耗一个构造函数(Constructor)的调用、一个复制构造函数的调用(Copy Constructor)以及一个析构函数(Destructor)的调用的代价。

三、总结

理论结合实践才是真理。由于编译器的不同,产生结果可能会不同,在实际开发中遇到不确定的情况,多写测试用例。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值