Google C++编程风格指南(二)之函数的相关规范

来源:互联网 发布:广电网络云平台 编辑:程序博客网 时间:2024/05/29 04:52

1.内联函数的使用规范

定义:内联函数是指用inline关键字修饰的函数。在类内定义的函数被默认成内联函数。

特点:是编译器可能会将其内联展开,编译时,类似于宏替换,使用函数体替换调用处的函数名,以减少函数调用的开销,无需按通常的函数调用机制调用内联函数。

优点:当函数体比较小的时候,内联该函数可以令目标代码更加高效。

缺点:滥用内联将导致程序变慢,内联有可能使目标代码量增加或减,返取决于被内联的函数的大小。内联较短小的存取函数通常会减少代码量,但内联一个较大的函数(注:如果编译器允许的话)将显著增加代码量。在现代处理器上,由亍更好的利用指令缓存(instruction cache),小巧的代码往往执行更快。

使用inline函数应该遵循以下几点:
(1)内联函数最好不要超过10行;

(2)对于析构函数应慎重对待,析构函数往往比其表面看起来要长,因为有一些隐式成员和基类析构函数(如果有的话)被调用;

(3)递归函数不应该被声明为内联函数。原因是递归调用堆栈的展开并不像循环那么简单,比如递归次数在编译时可能是未知的,大多数编译器都不支持内联递归函数。

考察如下代码,一个简单的递归调用:

#include <iostream>using namespace std;inline void print(int n){    --n;    if(n>0)        print(n);    else         for(int i=0;i<5;++i)            cout<<"this is inline function:"<<i<<endl;}int main(){    print(10);}

在VS2012中转到反汇编,其汇编代码为:

int main(){002E12B0  push        ebp  002E12B1  mov         ebp,esp  002E12B3  and         esp,0FFFFFFF8h      print(10);002E12B6  call        print (02E1270h)  }

可见,即使将递归函数print(int n)定义为inline函数,实际上编译器并未将其内联展开,按照正常的函数去调用它。

(4)虚函数不应该被申明为内联函数。因为虚函数的调用较普通函数复杂,需要运行时通过查找虚函数表动态获取虚函数的入口地址,编译器编译阶段是不能确定虚函数的入口地址,故不能将其在编译时静态展开。

(5)如果对析构函数内联,主要原因是在类体重定义,为了方便抑或是其他原因,应对其行为给出文档说明。

2.函数相关规范

2.1函数参数顺序(Function Parameter Ordering)

定义函数时,参数顺序为:输入参数在前,输出参数在后。

C/C++函数参数分为输入参数和输出参数两种,有时输入参数也会输出(注:值被修改时)。输入参数一般传值或常数引用(const references),输出参数或输入/输出参数为非常数指针(non-const pointers)。对参数排序时,将所有输入参数置于输出参数之前。不要仅仅因为新添加的参数,就将其置于最后,而应该依然置于输出参数之前。

注意,返一点并不是必须遵循的规则,输入/输出两用参数(通常是类/结极体发量)混在其中,会使得规则难以遵循。

2.2不要设计多用途面面俱到的函数

多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。 应编写功能单一集中的函数。

2.3函数的规模

函数的规模尽量限制在80行以内 ,不包括注释和空格行。其次,避免设计多参数函数,不使用的参数从接口中去掉,其目的是为了减小函数接口的复杂度。

2.4尽量编写线程安全函数与可重入函数

2.4.1什么是线程安全函数

线程安全函数是多线程情况下,可安全的被多个线程并发执行的函数。

确保函数线程安全,主要需要考虑的是线程之间的共享变量。属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄存器。因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些共享变量进行访问时,如果要保证线程安全,则必须通过加锁的方式。

线程安全函数与线程不安全函数示例:

static int tmp;  //线程不安全函数void func1(int* x, int* y) {       tmp=*x;       *x=*y;       *y=tmp;  } //线程安全函数void func2(int* x, int* y) {       int tmp;       tmp=*x;       *x=*y;       *y=tmp;  }  

func1是线程不安全函数,因为func1在被多线程并发调用时,使用的共享变量tmp可能被其它线程的func1改变,从而导致函数结果的不确定性。解决办法就是给全局变量tmp加锁,或者使用私有局部变量,函数func2就这这样做的。

2.4.2什么是可重入函数

可重入函数的定义本人目前没有找到比较权威的参考资料,个人理解是:可以被中断处理函数调用的线程安全函数是可重入函数。关于可重入的概念,可以参考可重入.维基百科。

也就是说,可重入函数必定可以被安全的并发执行。安全指函数的运行结果必须满足预期,不存在不确定性。

对于百度百科的描述,实际上是介绍了一个特殊的场景下,满足这个场景的线程安全函数就是可重入函数。这个特殊的场景就是函数在响应中断期间,被中断处理函数再次调用,这就是“重入”,重新进入的形象描述。再次被调用可以安全的进行,这就是“可重入”。相反, 不可重入(non-reentrant)不可重入的后果主要体现在象终端处理函数需要重入的情况中,如果信号处理函数中使用了不可重入的函数,则可能导致程序的错误甚至崩溃。

要确保函数可重入,需满足以下几个条件:
(1)不在函数内部使用静态或全局数据;
(2)不返回静态或全局数据,所有数据都由函数的调用者提供;
(3)使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据;
(4)不调用不可重入函数。

2.4.3可重入函数与线程安全函数的区别

(1)关系
可重入函数是线程安全函数的子集,即可重入函数一定是线程安全函数,线程安全函数不一定是可重入函数。

关系图如下:
这里写图片描述

(2)区别
在线程安全函数可以对共享地址空间数据加锁,可重入函数则不能。因为在可重入函数响应中断时,中断处理函数若再次调用该函数时,会发生死锁。

其它的区别,本人暂未发现,理解到,若有人知晓,希望勿吝赐教,留言告知。

在多线程条件下,应当做到函数是线程安全的,更进一步,做到可重入 。


参考文献

[1]百度百科.可重入函数
[2]百度文库.Google C++编码规范中文版

1 0