定义内联函数的正确方法是什么

What is the correct way to define inline functions?

本文关键字:方法 是什么 函数 定义      更新时间:2023-10-16

假设我有一个类X(X.h):

class X {
  private:
    unsigned unitsSold = 0;
    double revenue = 0.0;
  public:
    double avgPrice();
}

avgPrice()应以何种方式定义?

选项1(课堂):

X.h

class X {
  private:
    unsigned unitsSold = 0;
    double revenue = 0.0;
  public:
    double avgPrice() {
      return unitsSold ? revenue / unitsSold : 0;
    }
}

选项2(在与类相同的文件中,但在类定义之外):

X.h

class X {
  private:
    unsigned unitsSold = 0;
    double revenue = 0.0;
  public:
    double avgPrice();
}
inline double X::avgPrice() {
  return unitsSold ? revenue / unitsSold : 0;
}

或选项3(在单独的头文件中):

X.h:

class X {
  private:
    unsigned unitsSold = 0;
    double revenue = 0.0;
  public:
    double avgPrice();
}

X-inl.h:

#include "X.h"
inline double X::avgPrice() {
  return unitsSold ? revenue / unitsSold : 0;
}

可能对inline说明符的含义有一些误解。是的,它确实向编译器提供了一个提示,即它更倾向于内联代码,而不是进行调用,但编译器并没有被迫遵守这个提示。inline说明符的主要用途是避免违反"一个定义规则"。

一旦声明了函数inline,就需要在它使用的每个翻译单元中定义它,并且每次定义都必须完全相同。这与您的标题所建议的相反——您定义函数的位置的选择决定了它是否需要标记为inline

1) 和2)是可以的。在第一种情况下,它是隐式的inline,而在第二种情况下你明确声明了它。

只有当您将X_impl.h编译并链接为源文件时,情况3)才会起作用。在这种情况下,将只有一个定义,并且inline将是冗余的。不过,这样编译器就不会在其他翻译单元中看到定义,这使得它不可能内联函数,无论它是否为inline

如果X_impl.h的目的是减少页眉的视觉大小,那么您应该反过来做,将其包含在X.h的末尾。inline必须留在这种情况下。

我会选择每种有利于可读性的方法,这取决于函数的大小:

  • 单行函数-->选项1
  • 小尺寸函数-->选项2
  • 中等大小函数-->选项3
  • 大尺寸函数-->您确定要内联吗

如果你有很多小尺寸的函数,选择选项3,千万不要把选项2和3混合在一起。

此外,当您提出第三个选项时,您必须记住包含X-inl.h而不是X.h。如果您修改如下:

X.h:

#ifndef _CLASS_X_H_
#define _CLASS_X_H_
class X {
  private:
    unsigned unitsSold = 0;
    double revenue = 0.0;
  public:
    double avgPrice();
};
#include "X-inl.h"
#endif

X-inl.h:

inline double X::avgPrice() {
  return unitsSold ? revenue / unitsSold : 0;
}

然后,您可以像往常一样包含X.h

这三个选项都是正确的。

之后它取决于这样的东西:

  • 如果函数很短(比如getters/ssetters),那么更常见的是在类定义中直接定义函数。

  • 如果你的函数更大,最好在另一个头中定义它,并在使用函数的源中只包含这个头。这只会加快编译速度。但CCD_ 16的大函数很少。

但不要忘记,并不是因为您使用了inline关键字,所以您的编译将内联您的函数。由编译器决定是否在它使用的每个地方都执行函数。

这在标准中有明确规定:

7.1.2/2函数说明符[dcl.fct.spec]

带有inline说明符的函数声明声明了一个内联函数。内联说明符向实现指示,与通常的函数调用机制相比,在调用点对函数体的内联替换更可取在调用点执行此内联替换不需要实现;然而,即使省略了这种内联替换,仍应遵守7.1.2中定义的内联函数的其他规则。

最后一件事:

大多数编译器已经对代码进行了优化,以便在更方便的时候生成内联函数。此说明符仅指示此函数首选内联的编译器。


正如jrok所说,inline主要用于避免违反一个定义规则。在这里,我们还可以引用标准的一小部分:

(§7.1.2/4)内联函数应在odr使用的每个翻译单元中定义,并且在任何情况下都应具有完全相同的定义(3.2)。

3.2一个定义规则[basic.def.odr]

任何翻译单元都不得包含任何变量、函数、类类型、枚举类型或模板的多个定义。

这取决于你在哪里使用内联函数,以及你使用它的频率。如果内联函数的代码很短(就像大多数getter/setter一样),如果它(可能)在很多地方使用,那么直接将它放入类定义中是最简单的方法。

如果你的内联函数是"巨大的",并且只被你类中的少数用户使用,那么把它放在一个单独的头中是最好的。这加快了编译速度。在这种情况下,不需要内联,但需要将额外的头文件与lib的用户进行通信。

我认为inline关键字和内联函数/方法可能有一些混淆。

关键字inline告诉编译器应该将函数/方法代码复制到调用函数/方法的位置。示例:

/* [...] */
inline void sayHello() {
    std::cout << "Hello" << std::endl;
}
int main(int argc, char **argv) {
    sayHello();
    return 0;
}

应成为

/* [...] */
int main(int argc, char **argv) {
    std::cout << "Hello" << std::endl;
    return 0;
}

编译时。但是编译器并没有被强制内联函数/方法。

另一方面,内联方法是在您声明它的地方实现的

/* [...] */
class X {
private:
    unsigned int unitsSold;
    double revenue;
public:
    /* [...] */
    double avgPrice() {
        if (unitsSold == 0) {
            return 0.0;
        }
        return revenue/unitsSold;
    }
};

只要您向编译器添加inline建议,您提供的三个选项将(必须)导致相同的编译代码。

这总是考虑一个标准编译器执行标准编译任务。。。