C++临时对象的产生及优化

目录

1.临时对象

2.临时对象的产生

3.优化

4.总结


1.临时对象

        Temporary object,临时对象。一听名字就明白,这个对象的意义不大,只是临时中转一下或者存在一下,有的可能连个存在感都刷不到就消失了。但不要小看这种临时对象,对C/C++这种以效率严苛为前提的编程环境下,它就是效率低下的某种代名词。但是,临时对象又无法完全避免,所以,怎么控制并减少临时对象的产生就是一个技术活儿了。

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

2.临时对象的产生

        那么,在什么情况下会产生临时对象呢?在不同的编译器和不同的标准下,可能都有所不同,下面就分析一下产生临时对象的具体场景:
        1.参数传递
        一般值传递对象都会产生临时的对象:

#include <iostream>

class Teacher {
public:
  Teacher() { std::cout << "call Teacher construct func,old_ default is:" << old_ << std::endl; }
  Teacher(int old) : old_(old) { std::cout << "call Teacher construct func,set old_ is:" << old_ << std::endl; }
  Teacher(const Teacher &t) { std::cout << "call Teacher copy construct func" << std::endl; }
  ~Teacher() { std::cout << "call Teacher deconstruct func!" << std::endl; }

public:
  int old_ = 10;
};
void TestPars(Teacher t) {
  std::cout << "Teacher old is:" << t.old_ << std::endl;
}

int main() {

  TestPars(11);
  return 0;
}

注意,编译时如果版本太高需要进行限制,比如此工程的运行环境是C++17,需要使用禁用优化并限定版本编译:

g++ -std=c++11 -fno-elide-constructors -g -o test main.cpp

运行结果:

call Teacher construct func,set old_ is:11
call Teacher copy construct func
Teacher old is:10
call Teacher deconstruct func!
call Teacher deconstruct func!

        2.返回值
        产生临时变量最容易就是在返回对象时:

#include <iostream>

class Teacher {
public:
  Teacher() { std::cout << "call Teacher construct func,old_ default is:" << old_ << std::endl; }
  Teacher(int old) : old_(old) { std::cout << "call Teacher construct func,set old_ is:" << old_ << std::endl; }
  Teacher(const Teacher &t) { std::cout << "call Teacher copy construct func" << std::endl; }
  ~Teacher() { std::cout << "call Teacher deconstruct func!" << std::endl; }

public:
  int old_ = 10;
};

Teacher GetTeacher() {
  Teacher t;
  return t;
}
int main() {
  //Teacher t1;
  //GetTeacher() = t1;//OK,可以测试并想一下
    Teacher t = GetTeacher();//注意:此处t不声明照样产生临时变量
    return 0;

}

运行结果:

//注意:使用C++17编译器则始终是两个析构函数
tempObject$ g++ -std=c++11 -fno-elide-constructors -g -o test main.cpp
tempObject$ ./test
call Teacher construct func,old_ default is:10
call Teacher deconstruct func!
call Teacher deconstruct func!
call Teacher deconstruct func!

        3.类型转换
        不同类型之间的隐式转换一般也可能产生临时对象 :

int main() {
  //Teacher tmp;
  //tmp = 88;
  Teacher tmp = 88;
  return 0;
}

运行结果:

call Teacher construct func,set old_ is:88
call Teacher copy construct func
call Teacher deconstruct func!
call Teacher deconstruct func!

这种情况和环境有比较大的关系,一般推荐是先声明对象变量,然后再用这个变量设置值,可在实际采用非优化的情况下,二者一致。

        4.中间计算结果

#include <iostream>

class Teacher {
public:
  Teacher() { std::cout << "call Teacher construct func,old_ default is:" << old_ << std::endl; }
  Teacher(int old) : old_(old) { std::cout << "call Teacher construct func,set old_ is:" << old_ << std::endl; }
  Teacher(const Teacher &t) { std::cout << "call Teacher copy construct func" << std::endl; }
  Teacher operator+(const Teacher &t1) {
    Teacher t;
    t.old_ = old_ + t1.old_;

    return t;
    // return Teacher(old_ + t1.old_);
  }
  ~Teacher() { std::cout << "call Teacher deconstruct func!" << std::endl; }

public:
  int old_ = 10;
};

void GetSum() {
  Teacher t1, t2, t3;
  Teacher t = t1 + t2 + t3;
  std::cout << "sum is:" << t.old_ << std::endl;
}

int main() {

  GetSum();
  std::cout << "cacl temp create!" << std::endl;
  return 0;
}

运行结果:

call Teacher construct func,old_ default is:10
call Teacher construct func,old_ default is:10
call Teacher construct func,old_ default is:10
call Teacher construct func,old_ default is:10
call Teacher construct func,old_ default is:10
call Teacher deconstruct func!
sum is:30
call Teacher deconstruct func!
call Teacher deconstruct func!
call Teacher deconstruct func!
call Teacher deconstruct func!
cacl temp create!

        5.函数const引用参数

        这个也是隐式转换:

void TestConstTmp(const std::string &s) { std::cout << "tmp value:" << s << std::endl; }
int main() {
  char buf[] = "this is test!";
  TestConstTmp(buf);
  return 0;
}
//或者
void TestConstTmp(const Teacher &t) { std::cout << "tmp value:" << t.old_ << std::endl; }

int main() {
  TestConstTmp1(88);
  return 0;
}

不过这种在实际模拟中未能模拟出临时对象的现象,所以待验证。而且需要注意的是,必须是const &方可。

3.优化

1)返回值优化(Return Value Optimization, RVO)

编译器可以优化返回对象的复制过程,避免创建临时对象。

class MyClass {
public:
    MyClass() {}
    ~MyClass() {}
};
 
MyClass createMyClass() {
    return MyClass(); // 不会创建临时对象
}

2)命名返回值优化(Named Return Value Optimization, NRVO)

类似于RVO,但适用于命名返回值。

MyClass createMyClass() {
    MyClass result;
    return result; // 不会创建临时对象
}

3)对于传参产生的临时对象,可以采用引用或指针传参来优化

        当函数参数或返回值可以通过引用或指针传递时,考虑使用它们而不是值传递。这可以避免复制操作,并允许函数直接操作原始数据。

4)移动构造函数(Move Constructor)

使用C++11的右值引用,可以通过移动语义来转移资源所有权,而不是复制。

class MyClass {
public:
    MyClass() {}
    MyClass(const MyClass&&) { /* 移动构造函数实现 */ }
    ~MyClass() {}
};

5)移动赋值操作符(Move Assignment Operator)

类似于移动构造函数,移动赋值操作符也可以转移资源所有权。

class MyClass {
public:
    MyClass() {}
    MyClass& operator=(const MyClass&&) { /* 移动赋值操作符实现 */ }
    ~MyClass() {}
};

6)使用std::move

        当资源可以“移动”而不是“复制”时(例如,当处理包含动态分配内存的类时),使用std::move可以将资源的所有权从临时对象转移到目标对象,从而避免不必要的复制操作。

MyClass a;
MyClass b = std::move(a); // a现在可能是不确定状态,所以使用后需要确保不再使用a

7)对于中间值计算及其它,可以在代码层面避免

比如不采用多于两上的连加,不使用隐式类型转换等等。

8)在STL库容器增加元素增加了emplace系列,用它来替换insert,push等,减少临时对象的产生。

9)避免不必要的类型转换

        尽量减少隐式类型转换和显式类型转换的使用,因为它们可能会创建不必要的临时对象。如果可能的话,使用模板和函数重载来提供不同类型的接口,以减少类型转换的需要。

4.总结

        优化是一个迭代的过程,需要根据具体的代码和用例进行调整。在优化之前,最好先测量代码的性能,以便了解优化的效果。同时,也要注意不要过度优化,以免引入难以维护的代码或产生意外的副作用。

  • 14
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
回答: 在C++中,临时对象和匿名对象是两个不同的概念。 临时对象是在表达式中创建的对象,它们在表达式结束后会被销毁。临时对象通常用于函数返回值、函数参数传递和运算符重载等情况。例如,在例子1中,A(10)就是一个临时对象,它在调用myshow()函数后会被销毁。 匿名对象是没有被命名的对象,它们通常用于临时操作,不需要被引用或使用。匿名对象在创建后会立即被销毁。例如,在例子1中,A(11)和A(12)都是匿名对象,它们在被赋值给a和b后会被销毁。 临时对象和匿名对象的生命周期取决于它们的使用方式和上下文。在例子3中,Cat cc = Cat()创建了一个匿名对象,并将其赋值给cc对象,这个匿名对象的生命周期就变成了cc对象的生命周期。 总结起来,临时对象是在表达式中创建的对象,而匿名对象是没有被命名的对象。它们的生命周期取决于它们的使用方式和上下文。 #### 引用[.reference_title] - *1* *3* [C++匿名对象](https://blog.csdn.net/u014583317/article/details/108705360)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [C++基础——匿名对象介绍、拷贝对象时的一些编译器优化](https://blog.csdn.net/weixin_69283129/article/details/127823416)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值