在类定义内部和外部定义成员函数之间有区别吗

Is there a difference between defining member functions inside vs outside the class definition?

本文关键字:定义 函数 之间 有区别 成员 外部 内部      更新时间:2023-10-16

考虑以下四个成员函数声明和定义:

// ==== file: x.h
#ifndef X_H
#define X_H
class X {
 public:
  int a(int i) { return 2 * i; }
  inline int b(int i) { return 2 * i; }
  int c(int i);
  int d(int i);
};
inline int X::c(int i) { return 2 * i; }
int X::d(int i) { return 2 * i; }
#endif

为了完整起见,这里有一个.cpp文件,它实例化一个X并调用方法。。。

// ==== file: x.cpp
#include "x.h"
#include <stdio.h>
int main() {
  X x;
  printf("a(3) = %dn", x.a(3));
  printf("b(3) = %dn", x.b(3));
  printf("c(3) = %dn", x.c(3));
  printf("d(3) = %dn", x.d(3));
  return 0;
}

我的问题是:这四种方法之间有什么显著的区别吗?我从这篇文章中的一条评论中了解到,编译器可能会自动内联类定义中定义的方法。

更新

许多答案都假设我在问内联和非内联之间的区别。我不是。正如我在最初的文章中提到的,我理解在头文件中定义一个方法会给编译器内联该方法的许可。

我(现在)也理解了方法d的风险:因为它不是内联的,所以如果有多个翻译单元,它将被多重定义。

我的问题仍然是:这四种方法之间是否存在显著的差异?(如前所述,我知道方法d不同)。但是,同样重要的是,是否有风格或习惯性的考虑因素会让开发人员选择其中一个而不是另一个?

由于这个答案不断获得好评,我觉得有义务改进它。但我添加的大部分内容已经在其他答案和评论中说明了,这些作者值得称赞。

关于将函数体放在类定义内部还是放在它下面(但仍在头文件中)之间的区别,有三种不同的情况需要考虑:

1) 该函数不是模板,也未声明为内联函数。在这种情况下,它必须在类定义或单独的cpp中定义,否则一旦尝试在多个编译单元中包含h,就会出现链接器错误。

2) 该函数是一个模板,但未内联声明。在这种情况下,将主体放在类定义中向编译器提供了一个提示,即函数可以内联(但最终决定仍由其自行决定)。

3) 函数被声明为内联函数。在这种情况下,没有语义差异,但有时可能需要将函数体放在底部,以适应依赖循环。

原始答案,提供了很好的信息,但没有解决实际问题:

您已经注意到了内联差异。此外,在头中定义成员函数意味着每个人都可以看到您的实现。更重要的是,这意味着每个包含您的头的人都需要包含使您的实现工作所需的一切。

如果你无论如何都要内联它,那么如果你想在一个屏幕上看到所有成员,或者你有下面提到的循环依赖关系,你应该把它移出类。如果您不想内联它,那么您必须将它从类中移到实现文件中。

在类循环引用彼此的情况下,可能不可能在类中定义函数以便内联它们。在这种情况下,为了达到同样的效果,您需要将函数移出类。

不编译:

struct B;
struct A {
    int i;
    void foo(const B &b) {
        i = b.i;
    }
};
struct B {
    int i;
    void foo(const A &a) {
        i = a.i;
    }
};

进行编译,并达到相同的效果:

struct B;
struct A {
    int i;
    inline void foo(const B &b);
};
struct B {
    int i;
    inline void foo(const A &a);
};
inline void A::foo(const B &b) {
    i = b.i;
}
inline void B::foo(const A &a) {
    i = a.i;
}

Oops,刚刚意识到您在头文件中有定义。如果包含文件包含在多个位置,则会产生问题。

如果函数是在CPP文件中定义的,则没有区别。

内联实现函数的唯一意义是当函数非常琐碎和/或具有性能影响时。

对于所有其他时间,最好将它们放在.cc文件中,并保持其实现不向类的用户公开。

正如user3521733所指出的,当存在循环依赖关系时,不可能在头文件中实现某些功能。在这里,您被迫将实现放在一个.cc文件中。

更新

就编译器和运行时而言,在类主体内部定义函数与在类主体外部定义函数时使用inline在类主体之外定义函数之间没有任何区别。

X::aX::bX::c都是inline d。X::d不是。这是这些函数之间唯一真正的区别,除了它们都是不同的函数。在报头中定义X::c这一事实并不重要。与此相关的是,该定义标记为inline

为了理解差异,了解inline是什么和不是什么很重要。inline不是性能调整。这不是为了让你的代码更快,也不是为了把代码以内联的方式输出。

它是关于ODR的。标记为inline的函数在使用它的每个翻译单元中将具有完全相同的定义。

当您尝试在两个或多个CPP文件中#include上面的文件,并在这些翻译单元中调用X::d时,就会出现这种情况。链接器会抱怨X::d被定义了不止一次——您违反了ODR。解决方法是标记函数inline,或者将定义移动到它自己的翻译单元。(例如,到CPP文件)