inline, const, virtual, static四个关键字使用时应该放在哪里?

注意,下面说的“声明”指的是非定义处的声明,别跟我扯什么“所有的定义都是声明”什么的。

1. inline
inline可以放在声明处,也可以放在定义处,也可以两个地方都放。但是inline是属于实现部分的内容,不应该出现在接口处,即类的使用者从使用的角度不需要知道是不是inline的。所以,编程规范是inline关键字应该只出现在定义处,而不应该在声明处,所以class body里不应该出现“inline”。这一点在C++ FAQ(http://www.parashift.com/c++-faq/)[9] Inline functions中有提到。BTW,所有在class body中实现的函数都是隐含inline的,因而在class body里实现的短函数也不需要显式的写上“inline”。所以可以得出结论:任何情况下都不应该在class body里看到“inline”这个关键字(注释除外)。新手也许会问,如果我在a.cc里定义了一个inline函数fun(),在b.cc里forward declare并使用了fun。按照编程规范,b.cc中对fun()的declaration处不标inline,那b.cc中使用fun()时,编译器怎么知道该把fun()内联展开呢?答案是inline函数不能像普通函数那样在另一文件中forward declare后调用,使用inline函数的唯一方法就是include(直接或间接)其定义,所以在定义处标inline就够了。

定义类的时候,如果inline member function的实现较短,可以和class body放在一个.h文件里;如果实现较长,则不应该和class body堆在一起,可以单列一个-inl.h文件存放inline函数的定义。注意:inline函数为了能够在编译时展开(即被inlined),所有使用该inline函数的地方都需要它的定义。所以inline函数的定义是放在.h文件中的,而不是像非inline成员函数放在.cc/.cpp文件中。

2. const
const必须同时出现在声明处和定义处,定义const (static) data member或const member function时都是,否则会被认为是不同的两个函数而可能导致编译错误。一个常识:全局const变量一般是放在头文件中的。(原因可参见《C++ Primer 4th.ed》2.4和2.9节。)关于const比较麻烦的是类中的static const数据成员,这个在static部分再详细解释。

3. virtual
virtual关键字只允许放在class body里的声明处。如果virtual在定义处出现会导致编译错误(CE)。如果一个函数fun1在类Base1中被声明为virtual的,那么Base1所有的子类以及子类的子类中的fun1都是virtual的——不管有没有再在这些子类的fun1上标明virtual关键字。但是,编程规范是:要在这些子类中的fun1中也明确写上virtual。如果不这样做,当想确定一个类的某个函数是不是virtual时,需要一层一层的检查它的父类中的相应函数是不是virtual——这显然不好。

4. static
static关键字的用法很多,这里只讨论定义类的static成员这一应用。static关键字只允许放在class body里的声明处。如果static在定义处出现会导致CE——这一点和virtual关键字一样。
Static函数成员声明的定义需要遵循的规范和普通成员函数一样。如果写在class body里,也会被隐式加上inline
static数据成员除了定义和声明这两个活之外,还有个initialization的事。non-const static member不允许在class body里初始化,否则编译错误。所以non-const static member的初始化要放在class body外的定义处。对于const static member(注意,代码里是static在const前,如static const int kMyConst;),其初始化可以在声明处也可以在定义处,但是不能两处都有,否则CE(duplicate initialization error)。这里有一个问题:既然const的static data member可以在class body里面初始化,当在class里面初始化时,class外的definition可不可省略呢?按《C++ Primer 4th.ed》12.6.2节说是不可以的,必须还要在类的外面定义一下;但是我用g++试了一下,不写外面的定义也可以编译通过——class body中带初始化的声明此时被当成了定义。为了规范和保险,也为了和non-const static data member统一,编程规范是外面的定义仍然保留。另外要注意的是:在class body里初始化const static member时,初始化式必须是编译时可以确定的;而在定义处初始化的时候,可以是任意表达式,比如一个返回键盘输入数据的函数。所以,只有在class body里进行初始化的const static data member才可以用在要求编译时值确定的常量处(如switch语句的case标号,template的数值形参)。
注意:所有static函数和数据成员,无论是不是const的,在class body之外的定义都不能放在.h文件中(当然除非是inline的),否则多处include时就会出multiple definition错误。一般应该把它们放在定义普通成员函数的.cc/.cpp文件中。这里的容易犯的错误是以为const static data member和全局域的const变量一样是默认file local的,而把const static data member也放在.h文件中。那是不对的!
总结一下:定义类的static成员时的编程规范是
(1) static关键字只放在class body里的声明处,而不出现在定义处;const关键字必须在声明处和定义处都出现;
(2) static成员,无论是不是const的,其定义一律放在.cc/.cpp文件中,不可省略,也不可放在.h文件中;
(3) non-const static data member的initialization只允许放在class外面的定义处。const 的static data member的initialization一般也放在外面的定义处;当该初始值是编译时可确定的,并且对用户有意义时(即能被用户理解,有助于用户明白该const的含义时),或者是该data member在后面的使用要求它必须是编译时值可确定的,那初始化就应该放在class里的声明处。

5. 以上几个关键字的组合使用:
(1) 对于变量而言,virtual和inline没有意义,所以只有static const的组合,该组合已经在上面详细分析过了。提醒一下,两个关键词的位置遵照各自的规则:“static”只写在声明处,const两个地方都要写。
(2) 对于非成员函数,const和virtual没有意义 ,而本文对static又只讨论定义类的static成员这一应用,所以没有适用的组合。
(3) 对于成员函数:
     a) 如果是static的,那就不允许再声明为const的。另外static也不能和virtual同时使用。 我觉得这两点本质上都是人为规定,而不是逻辑上的显然。比如《C++ Primer 4th.ed》说static的const成员函数显然没有意义,因为const是说不允许该成员函数修改所属的对象(object),而static成员不属于任何对象。但其实C++也可以规定const可以用于标明一个static函数不允许修改class的static数据成员——虽然事实上没有这样规定。这样涉及static的组合只有一种——inline static。使用的时候两个关键词的位置遵照各自的规则:static只写在声明处,inline只写在定义处。Inline static成员函数也是一种inline函数,所以其定义和其他inline函数一样,应当放在.h文件中。
     b) 如果不是static的,那就可以const, virtual, inline任意组合。Const自己起自己的作用,与virtual, inline无关。所以只有inline virtual一种组合需要讨论。把virtual函数定义成inline的是没有问题的,但是这种组合比较有意思:inline virtual函数的virtual性质仅在对象的动态类型和静态类型可能不同时(即通过基类的指针或引用调用时)才会起作用,而其inline性质仅在对象的动态类型和静态类型一定相同时才会起作用,即内联展开。所以这两个关键词虽然可以组合使用,但是从来都不会同时发挥作用。至于需要遵循的代码规范,应该是各自遵守各自的规范,且inline性质决定其定义要放在.h文件中——不过我没有实验过,如果有读者找到了相关权威资料或者有实验结果,请告诉我。 

6. 备注:

本文主要目的是总结这些关键字如果要用的话该放在哪,以及相应的声明、定义、初始化怎么写、写在哪,而不是要讲为什么要用(或者说什么时候应该用)它们。比如virtual——理论上任意非static成员函数都可以加virtual(构造函数除外)。但是,如果你给复制操作符operator=加上了virtual,那你就走的很远了。(走的有多远详见《C++ Primer 4th.ed》15.4.4节)同时,如果一个类有virtual成员,但是你没有给该类的析构函数加上virtual,那你就死的很惨了。(死的有多惨详见《C++ Primer 4th.ed》15.4.4节)inline到底什么时候该用那解释起来更是一坨——用g++编译时递归函数都可以内联展开你信不信?(具体怎么展开详见《般若波罗蜜心经》)关于用还是不用inline的结论基本上就是:你用了会走的很远,你不用会死的很惨。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值