问题 我应该在哪里实现我的类方法?


我知道我的类方法有三种不同的实现“位置”:

1)在我的类(.h文件)中定义方法并在我的.cpp文件中实现它

//.h
class Foo
{
  int getVal() const;
};


//.cpp
int Foo::getVal() const
{ return 0; }

2)在我的类(.h文件)中定义并实现该方法。

//.h
class Foo
{
  int getVal() const
  { return 0; }
};

3)在我的类中定义方法并在类外但在我的头文件中实现它。

//.h
class Foo
{
  int getVal() const;
};

int Foo::getVal() const
{ return 0; }

这三种方法有哪些主要区别?


11235
2018-02-02 17:45


起源

#3可能会导致链接器错误(只要将此标头包含在多个源文件中)。 - Igor Tandetnik
#2将允许编译器在适用的情况下内联函数(无需整个程序优化),因此它通常用于您在示例中显示的小型getter / setter函数。对于模板化类(仅限),#1不是一个选项,#2和#3相同。 - Cameron
要获得更快的编译速度,请尽可能选择#1。 - Neil Kirk
相关问题: 这里 和 这里 - anatolyg
请注意,在您说“定义”的地方,适当的术语是声明。函数的定义包括函数体。 - Jon Kalb


答案:


这个问题有三个要素:可读性(代码看起来有多好),编译(编译器可以优化它多少)和实现隐藏(如果你使用代码作为库,你可能不想明确地分享你的特殊酱和世界一起)。

方法一只公开头文件中函数的接口。这意味着您将显示一个漂亮的干净界面,并且您的实现不会以纯文本形式公开。但是,代码不能跨编译单元内联,因此它有可能在运行时稍慢(实际上,这仅对非常非常非常小的代码百分比很重要)。这应该是你的默认方式。

方法2是隐式内联。冗长的功能会让你的课程变得混乱,因为(imho)很糟糕。还将您的实现暴露给全世界。但是,该函数可以内联,并且比在另一个地方定义它更简洁。我保留这个非常小的功能。

方法3实际上是非法的,因为你将打破一个定义规则,但以下是好的:

//Foo.h
class Foo {
  int getVal() const;
};

inline int Foo::getVal() const { 
  return 0; 
}

当我想保持类定义干净但想要头文件中的函数定义(对于inlinable或模板函数)时,我使用它。


9
2018-02-02 18:02



几乎任何现代链接器都可以跨编译单元内联。 - Mooing Duck
此外,模板改变了一切,值得一提 - Mooing Duck
@Mooing Duck,是的,但你需要在内联后运行其他优化传递以获得最佳效果,并且只能通过链接时优化来完成。 - sbabbi
@sbabbi:这就是为什么我说链接器会这样做的原因。这篇文章说“代码不能跨编译单元内联”,这是不正确的。 - Mooing Duck


(1)将为一个大型项目编译得更快(只需要编译定义 getVal 一旦进入Foo.cpp,只需要重新编译一件事,如果定义发生变化),你就可以为想要查找它的人获得一个非常清晰的类。另一方面,你不能内联 getVal()

(2)和(3)将编译得更慢,并为您的定义更改添加更多依赖项。但你可以内联 getVal()。如果这是必需的 getVal 是一个模板功能。 注意 (3)如果多次包含标题会导致链接器错误 - 你必须记住记住标记你的功能 inline。这是偏好(1)和(2)至(3)的一个很好的理由。

真的,这不是挑选(1)vs(2)的问题。您可能会在大型项目中使用它们 - 将标题中应该内联的函数的定义(和模板)放在标题中,并将内联中的内容放在cpp中。


2
2018-02-02 17:51



这个答案是不完整的。它只描述编译速度,这不是唯一的问题,可能不是最重要的问题。 - anatolyg
@anatolyg对于我需要几分钟构建的项目,这是最重要的问题。 - Neil Kirk
答案中唯一缺少的是如果标题包含多次3)可能的ODR问题;不同意@anatolyg的评论并且已经上传了 - Stefan Atev
@anatolyg答案并不“仅”描述编译速度。这是“最重要的”问题纯粹是一个意见问题,也是所讨论项目的实际功能。 - Barry


在我开始之前的一个小注释,有很多单词用来描述这些东西非常相似,我将使用头文件中的部分声明(int getVal() const)和cpp文件中部分的实现(int Foo::getVal() const)。如果这些不完全准确,请道歉。

另请注意,在不明确的情况下,福利是对其他人的处罚。

1)在我的类(.h文件)中定义方法并在我的.cpp文件中实现它

这被认为是标准方法,并且通常被认为是默认方法(有许多例外,因此默认可能有点强)。

它将声明与实现分开。这提供了一些好处:

  • 您只需要编译一次实现。这可能会节省编译时间。
  • 您只需要参考声明。这可以避免排序问题,对于类之间的循环引用至关重要。
  • 您不会分发您的实现,这对于闭源系统很有用,因为您经常需要分发大量的.h文件,以便其他人插入您的系统。

2)在我的类(.h文件)中定义并实现该方法。

这称为内联实现,它应该仅用于简单实现。它也有一些好处:

  • 大多数编译器都将此视为一个巨大的内联提示。它不保证inling但很可能,对于简单的方法来说,这可能是一个胜利。属性样式方法是一个常见的例子。
  • 你的实现很容易找到,因为它在声明中是直截了当的。

3)在我的类中定义方法并在类外但在我的头文件中实现它。

我以前没有见过这个,但是假设它在定义不重要时使用但你想要2的内联优点。 IdeaHat 提到了 inline 在这种情况下需要关键字。


0
2018-02-02 18:06



#3对于复杂模板很常见。你有一个相对正常的类定义,然后是复杂的函数定义。 。中不需要inline关键字 template 案件。 - Mooing Duck