写出高效代码的12条建议

180次阅读  |  发布于3年以前

今天和大家介绍一下能让C++代码更加高效的几个小技巧,话不多说,以下为本文目录:

以下为正文:

值传递还是引用传递:

一般情况下使用const的引用参数。对于函数本身会拷贝的参数,最好使用值传递,但只有当参数的类型支持移动语义时才这样。

在某些情况下,值传递并移动实际上是向函数传递参数的最佳方式(注意看后面的tips),例如:

class A {
   public:
    A(const std::string &str) { str_ = str; }

   private:
    std::string str_;
};

可以考虑改为这种形式:

class A {
   public:
    A(std::string str) { str_ = std::move(str); }

   private:
    std::string str_;
};

因为无论如何都会对它们进行拷贝。

tips:看有些资料说后者值传递是更好的参数传递方式,貌似有些道理,但是我没找到非常合理的理由,有知道的读者可以在评论区留言。

按值返回还是按引用返回:

可以通过从函数中按引用方式返回对象,以避免对象发生不必要的复制。但有时不可能通过引用返回对象,例如编写重载的operator+和其他类似运算符时。

永远都不要返回指向局部对象的引用或指针,局部对象会在函数退出时被销毁。

但是,按值返回对象通常没啥大问题。因为一般情况下他们会触发返回值优化或移动语义,即不会有多余的拷贝动作。

使用移动语义:

尽量确保对象拥有移动构造函数和移动赋值运算符。对象有了移动语义后,许多操作都会更加高效,特别是与标准库和算法相结合时。

避免创建临时对象:

没有必要的临时对象能避免就避免。一般来说,应该避免迫使编译器构造临时对象的情况。尽管有时这是不可避免的,但是至少应该意识到这项“特性”的存在,这样才不会为实际性能和分析结果而感到惊讶。编译器还会使用移动语义使临时对象的效率更高。这是要在类中添加移动语义的另一个原因。

《More Effective C++》第19条款中介绍过:所谓的临时对象并不是程序员创建的用于存储临时值的对象,而是指编译器层面上的临时对象:这种临时对象不是由程序员创建,而是由编译器为了实现某些功能(例如函数返回,类型转换等)而创建。

比如下面的代码就会有临时对象的产生:

void Func(const std::string& s);
char arr[]="hello";
Func(aar); // here

返回值优化

通过值返回对象的函数可能导致创建一个临时对象。看下面的代码:

Person createPerson()
{
    Person newP { "Marc", "Gregoire", 42 };
    return newP;
}

假如像这样调用这个函数(假设Person 类已经实现了operator<<运算符):

cout << createPerson();

即便这个调用没有将createPerson()的结果保存在任何地方,也必须将结果保存在某个地方,才能传递给operator<<。为此编译器创建一个临时变量,来保存createPerson()返回的Person 对象。

即使这个函数的结果没有在任何地方使用,编译器也仍然可能会生成创建临时对象的代码:

createPerson();

编译器可能生成代码来创建一个临时对象来保存返回值,即使这个返回值没有使用也是如此。

不过吧,编译器会在大多数情况下优化掉临时变量,以避免复制和移动。

关于返回值优化我之前有篇文章介绍过,感兴趣的可以看看这个:《[左值引用、右值引用、移动语义、完美转发,你知道的不知道的都在这里] 》

预分配内存:

比如标准化容器中的reserve,需要频繁创建内存的地方可以考虑预分配一块内存出来,避免频繁的创建内存。

内联函数:

短函数可以使用内联消除函数开销。

迭代 vs 递归:

这里我更倾向于选择迭代方式,而不是递归。递归占用大量栈内存,且可能会产生很多不必要的临时对象构建。

选择效率更高的算法:

学计算机的估计没有不知道算法的吧,学算法估计没有人不知道如何计算时间复杂度和空间复杂度吧,在平时开发过程中遇到算法问题时我们可尽量选择效率更高的算法,比如O(N)和O(N2)的算法,我们肯定要选择O(N)的呀,这里可以了解下C++的,这里引入了很多高效的算法供我们使用。

尽可能多的使用缓存:

将某些数据保存下来供下次使用,避免再次获取或重新计算它们。如果任务或计算特别慢,应该保证不执行那些没有必要的任务或者重复计算。

做客户端开发的朋友应该都听说多级缓存的概念,就是这个原理。

profiling:

性能问题永远离不开profiling工具,多用profiling工具。工具篇我之前介绍过:《[这么多性能调优工具,看看你知道几个?] 》

other碎碎念:

A::A() : a_(a) {} // better

A::A() {
    a_ = a;
}

最后想说一句:

先完成再完美。

不要一开始就想着写最完美的代码,很多bug都是过早过度优化导致的。

一般情况下,性能较高的代码可读性都不是特别高。提早优化很可能引发很多bug。

很多情况下,我们可以先完成代码,确保功能完成且正确之后,再去考虑完善。

完成代码后,可以使用profiling工具,找到瓶颈所在,然后做相应优化。

另外产品和测试如果没给你提性能需求,那优化它干嘛!

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8