最近碰到一个与内联方法有关的编译问题,记叙以下。html
类Scheduler的实现以下所示,其中方法SetStates()仅仅被类自己使用(暂且先无论它的public属性)。程序员
// scheduler.hpp class Scheduler { public: Scheduler():m_state1(0),m_state2(0) {} ~Scheduler() {} inline void SetStates(int state1, int state2); int GetState1() { return m_state1; } int GetState2() { return m_state2; } private: int m_state1; int m_state2; };
// scheduler.cpp void Scheduler::SetStates(int state1, int state2) { m_state1 = state1; m_state2 = state2; }
如上代码构建正常。以后,新建一个新的类SchedulerMgmt,而且在其中使用了类Scheduler当中的SetStates()方法:app
// SchedulerMgmt.cpp void SchedulerMgmt::UpdateSchedulerState(int state1, int state2) { m_scheduler.SetStates(state1, state2); }
从新编译,提示错误:函数
scheduler.hpp:10: warning: inline function ‘void Scheduler::SetStates(int, int)’ used but never defined /tmp/ccsOhsNk.o: In function `SchedulerMgmt::UpdateSchedulerState(int, int)': scheduler-mgmt.cpp:(.text+0x111): undefined reference to `Scheduler::SetStates(int, int)' collect2: ld returned 1 exit status
因为编译错误的产生仅仅是在新增长类SchedulerMgmt,并在其中调用了Scheduler中的SetStates()以后才产生,同时该方法刚好声明为内联方法,很天然的便怀疑到这个方法的声明之上:inline。因而前后尝试了下面两种方法来排错:测试
测试的结果是第一种方法A无论用,编译错误依然存在,方法B解决了问题。编码
这样的结果让本身感到疑惑不已。众所周知,将方法声明为内联的方式有两种,其一为在class的定义当中定义成员方法;其二是使用inline关键字。第一种方式通过测试是可行的,然而为何第二种方式并无达到目的呢?难道问题出在外部调用内联方法上面?这是第一个疑惑。code
方法B在将SetStates()的内联属性去掉问题消失,这是不难理解的。但当我仅仅在其定义上加上inline修饰符,便又会出现问题,只是这个时候仅仅剩下连接的问题:htm
/tmp/ccoizmZA.o: In function `SchedulerMgmt::UpdateSchedulerState(int, int)': scheduler-mgmt.cpp:(.text+0x111): undefined reference to `Scheduler::SetStates(int, int)' collect2: ld returned 1 exit status
这是另一个疑惑。看来对于内联的许多细节,本身以前并未注意到。blog
在查阅了C++标准最新的Draft以后,从中找到了针对“方法A没有解决编译错误”的解释:get
An inline function shall be defined in every translation unit in which it is odr-used and shall have exactly the same definition in every case (3.2). [ Note: A call to the inline function may be encountered before its definition appears in the translation unit. —end note ].
这里提到内联函数的基本要求:任一调用该函数的地方均须要看到它的定义(这一点上,C++中的template也有这样的要求)。因此在每个编译单元都须要定义(注:在程序的构建工程当中,编译阶段会对每个源代码文件分别编译、汇编,以后将以后的输出文件进行连接等处理,这里的编译单元能够看作是一个个独立的源代码文件。)可见,将内联成员方法定义在类的定义里面是最为稳妥的。在方法A当中,类SchedulerMgmt中尽管能够看到SetStates()声明为inline,可是却没法见着它的定义,便提示错误。这便是如上第一个疑惑的解答案。
对于第二个疑惑,其实能够归结于一点:编译阶段对于类中的普通成员方法与内联成员方法的处理有差别,但差别在什么地方?要回答这个问题,能够从编译以后的结果上去看。
使用objdump -s -d scheduler.o分别查看输出修改先后的scheduler.cpp编译以后的汇编码:
可见,在将SetStates()定义为内联方法时,在编译以后的目标文件中并不会包含该函数的指令,也就是说编译器将一个方法内联以后,并不会在目标文件当中将其保留,就如同宏在预处理阶段直接文本扩展同样。所以,到这里第二个疑惑解开。
上面是笔记的主要内容。其实在参考资料<sup>2</sup>连接的一篇文章中介绍了有关extern inline的知识,权因本笔记是对工做中问题的一次小总结,因此再也不将其归入讨论。
g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-48) Copyright (C) 2006 Free Software Foundation, Inc.