是否应该内联代码?

10

我最近写了一些类,想知道在头文件中定义一些较小的成员函数是否是不良实践、影响性能、破坏封装或者还有其他任何本质上的缺点(我已经尝试了Google!)。下面是一个我写的头文件示例,其中包含了许多这样的函数:

class Scheduler {
public:
    typedef std::list<BSubsystem*> SubsystemList;

    // Make sure the pointer to entityManager is zero on init
    // so that we can check if one has been attached in Tick()
    Scheduler() : entityManager(0) { }

    // Attaches a manager to the scheduler - used by Tick()
    void AttachEntityManager( EntityManager &em )
        { entityManager = &em; }

    // Detaches the entityManager from a scheduler.
    void DetachEntityManager()
        { entityManager = 0; }

    // Adds a subsystem to the scheduler; executed on Tick()
    void AddSubsystem( BSubsystem* s )
        { subsystemList.push_back(s); }

    // Removes the subsystem of a type given
    void RemoveSubsystem( const SubsystemTypeID& );

    // Executes all subsystems
    void Tick();

    // Destroys subsystems that are in subsystemList
    virtual ~Scheduler();
private:
    // Holds a list of all subsystems
    SubsystemList subsystemList;

    // Holds the entity manager (if attached)
    EntityManager *entityManager;
};

那么,像这样内联函数有什么问题,还是可以接受的吗?

(另外,我不确定这是否更适合“代码审查”网站)


一些值得关注的东西 https://dev59.com/u3VC5IYBdhLWcg3w7Vtq - DumbCoder
有趣;但我更关注在头文件中内联的可读性/设计效果。 - Seb Holzapfel
这是一个好的编程实践,特别是当函数很少且它们被嵌套时。因为它们可以被优化掉。另外,如果在类声明后使用内联关键字列出它们,代码看起来更整洁。 - QuentinUK
1
我找到了一个很好的总结:https://dev59.com/MnVD5IYBdhLWcg3wL4cA - cschwan
7个回答

11

内联会增加耦合,并增加类定义中的“噪声”,使类更难以阅读和理解。通常情况下,内联应被视为一种优化措施,仅在分析器指示必要时使用。

有几个例外:如果抽象基类所有其他函数都是纯虚函数,我将始终内联虚析构函数;为仅包含公共数据成员且没有其他函数的“结构体” - 类提供内联构造函数似乎很愚蠢单独为一个空析构函数创建另一个源文件时,由于所有其他函数都是纯虚函数且没有数据成员,析构函数不会发生变化而需要进行其他更改。此外,在源文件中定义类而不是头文件中定义的类中,我对避免使用内联较少严格,因为在这种情况下耦合问题显然不适用。


好的回答;只是似乎很烦人,因为要为一个一行的函数编写三行额外的代码(在不同的文件中)。 - Seb Holzapfel
2
@Schnommus 对作者来说有点麻烦,但通常我在开发类时会在编辑器的不同窗格中打开源代码和头文件,这样跳转就很容易了。(我可以从头文件中复制/粘贴函数声明,删除尾部的;,然后从那里开始定义。)对于读者来说,我觉得知道要在源文件中查找实现,而不是在头文件中查找会更方便。 - James Kanze
1
我不同意将内联函数称作“噪音”的表述:看到一行函数形式上是一种文档,通常比注释更好。而且所引入的额外耦合非常低,因为一行函数的更改几乎总是伴随着类的数据结构(私有部分)的更改。 - thiton
4
对于试图理解类提供的契约的人来说,实现细节是噪音。像 return m_someInternalValue; 这样的实现根本没有说明返回的内容,需要注释来解释返回值的含义。请注意不要改变原始意思。 - James Kanze
3
反对内联的另一个论点是,如果头文件被广泛地#include,更改单个函数的实现可能会触发大部分项目的重建。对于小型项目,这并不是问题,但对于需要数小时编译的大型项目来说,这是一个巨大的问题。另一方面,如果函数定义在常规源文件中,那么只有当该函数被更改时,才会重新编译那个源文件。这也可能会减慢编译速度:即使未使用内联函数,每次包含头文件时都必须重新编译内联函数。 - Dan Moulding

6

您的所有成员函数都是一行代码,所以我认为这是可以接受的。请注意,内联函数实际上可能会使代码大小减小(!!),因为优化编译器会增加非内联函数的大小以使其适合块中。

为了使您的代码更易读,我建议按以下方式使用内联定义:

class Scheduler
{
    ...

    void Scheduler::DetachEntityManager();

    ...
};


inline void Scheduler::DetachEntityManager()
{
    entityManager = 0;
}

在我看来,这样更易读。

减少混乱技巧 ;) - A23149577

2
我认为内联(如果我理解你的意思正确,你指的是将琐碎的代码直接写入头文件中,而不是编译器的行为)通过以下两个因素提高了可读性:
  1. 它区分了琐碎的方法和非琐碎的方法。
  2. 它使琐碎方法的效果一目了然,成为自说明的代码。
从设计的角度来看,这并不重要。您不会更改内联方法而不更改subsystemList成员,并且在两种情况下都需要重新编译。内联不会影响封装性,因为该方法仍然是具有公共接口的方法。
因此,如果该方法是一个愚蠢的一行代码,没有必要进行冗长的文档记录或者可以想象到的不涉及接口变化的更改需求,我建议使用内联。

4
这样做是不正确的。一个函数是否“琐碎”属于实现细节,而不是您想要在头文件中公开的内容。类定义中的代码本身就是多余的干扰噪音,使得类定义更难阅读。即使必须进行内联以提高性能,最佳实践也是将函数定义放在类外部。 - James Kanze

1

这将增加可执行文件的大小,在某些情况下会导致性能变差。

请记住,内联方法要求其源代码对使用它的人可见(即在头文件中的代码),这意味着对内联方法实现的微小更改将导致重新编译使用定义内联方法的头文件的所有内容。

另一方面,对于经常调用的短方法,它是一种小型性能提升,因为它将节省调用方法的典型开销。

如果您知道在哪里使用它们并且不滥用它们,则内联方法是可以接受的。

编辑: 关于样式和封装,使用内联方法会阻止您使用诸如指向实现的指针、前向声明等内容,因为您的代码位于头文件中。


2
根据我的经验,在像例子中一样将单行函数内联到代码中可以使代码更小、更快,因为它避免了参数传递和函数调用。 - Bo Persson

1

内联至少有三个“缺点”:

  1. 内联函数与虚拟关键字不兼容(我的意思是,在概念上,你要么想要一段代码替换函数调用,要么想要函数调用是虚拟的,即多态的;无论如何,更多详情请参见this);

  2. 二进制代码会变得更大;

  3. 如果在类定义中包含内联方法,则会暴露实现细节。

除此之外,内联方法基本上是可以的,尽管现代编译器已经足够聪明,能够在性能需要时自动内联方法。因此,在某种程度上,我认为最好完全交给编译器处理...


2
一个 inline 成员函数可以是 virtual 的,当调用时它可能不会被内联,但这对于任何 inline 函数都是正确的,无论是否为 virtual - CB Bailey
@Charles Bailey:谢谢,我重新措辞回答以更好地反映我的想法,避免产生歧义。 - sergio
1
当我像问题中那样编码时,我的二进制文件会变小。我做错了什么? - Bo Persson

0

class体内的方法通常会自动被设置为inline。此外,inline只是一种建议而不是命令。编译器通常足够聪明,可以判断是否将函数设置为inline

您可以参考这个类似问题


类体内的方法是否自动为内联函数?还是在头文件中定义的方法才是内联函数? - eeerahul
@eeerahul,在类体内部定义的方法会自动进行内联。在其他情况下,您可以使用 inline 关键字。它保证编译器每个翻译单元(即 .cpp 文件)只生成一个函数副本。 - iammilind

0
实际上,如果函数太大,编译器会自动不将其内联,因此您可以在头文件中编写所有函数。只需在您认为最合适的位置编写函数体,让编译器决定即可。而且,inline关键字通常也会被忽略,如果您确实坚持要内联函数,请使用__forceinline或类似的东西(我认为这是微软特有的)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接