跨文件的代码组织,必须处理模板函数和内联

Des*_*ume 4 c++ compiler-construction templates inline

我正在维护一个大型的模板类库,它们可以根据任何一个floatdouble类型执行代数计算.许多类都有访问器方法(getter和setter)以及运行少量代码的其他函数,因此当编译器定位它们的定义时,需要将这些函数限定为内联.相反,其他成员函数包含复杂的代码,因此最好调用而不是内联.

函数定义的很大一部分位于头文件中,实际上是头文件所包含的.inl文件.但也有很多类,其功能定义,幸福地生活在.cpp文件通过显式实例化的手段floatdouble,这是相当的好东西在图书馆的情况下做的(这里解释为什么).最后,有相当多的类,它们的函数定义被分解为.inl文件(访问器方法)和.cpp文件(构造函数,析构函数和繁重的计算),这使得它们都很难维护.

只有当我知道一种可靠的方法来防止某些函数被内联时,或者在.cpp文件中,如果inline关键字可以强烈建议编译器内联一些函数,我当然会在.inl文件中实现所有类实现,当然,它才不是.我真的更喜欢库中的所有函数定义都驻留在.cpp文件中,但由于访问器方法在整个库中被广泛使用,所以我必须确保它们在被引用时被内联,而不是被调用.

所以,在这方面,我的问题是:

  1. inline考虑到这样一个事实,标记模板函数的定义是否有意义,正如我最近在这里学到的那样,它将被编译器自动限定为内联,无论它是否标记inline

  2. 最重要的,因为我想有聚集了模板类的所有成员函数的定义在一个文件一起,要么(使用显式实例中的.cpp的情况下),最好是.INL或的.cpp 仍然能够提示编译器(MSVC和GCC)应该内联哪些函数,哪些不应该,确定模板函数是否可以实现,如何实现这一点,或者如果真的没有办法(我希望有的话) ),最佳折衷方案是什么?

----------

EDIT1:我知道inline关键字只是建议编译器内联函数.

EDIT2:我真的知道.我喜欢向编译器提出建议.

EDIT3:我还是知道.这不是问题所在.

----------

鉴于一些新的信息,还有第三个问题与第二个问题密切相关.

3.如果编译器如此聪明,以至于他们可以更好地选择应该内联哪个函数以及应该调用哪个函数,并且能够进行链接时代码生成和链接时优化,这有效地允许他们查看.cpp -located函数定义在链接时决定其内联或调用的命运,那么一个好的解决方案可能只是将所有定义移动到相应的.cpp文件中?

----------

那么结论是什么?

首先,我很感谢Daniel Trebbien和Jonathan Wakely的结构和有根据的答案.投票两者但必须只选择一个.然而,给出的答案都没有给我一个可接受的解决方案,所以所选择的答案恰好是帮助我做出最终决定的其他答案,其中的细节将在下一个对任何感兴趣的人解释.

好吧,因为我一直在重视代码的性能而不是维护和开发的方便性,在我看来,最可接受的折衷方案是移动所有的访问器方法和其他每个的轻量级成员函数.将模板类放入相应标头所包含的.inl文件中,用inline关键字标记这些函数,以试图为编译器提供良好的提示(或使用关键字进行内联强制),并将其余的函数移动到相应的.cpp文件.

将所有成员函数定义放在.cpp文件中会妨碍内容轻量级函数,同时释放链接时优化的一些问题,正如Daniel Trebbien为MSVC(在较旧的开发阶段)和Jonathan Wakely为GCC所确定的那样(在目前的发展阶段).并且将所有函数定义放在头文件(或.inl文件)中并不会超过将每个类的实现分类为.inl和.cpp文件以及此决策的额外副作用的总体好处:它将确保只有原始访问器方法的代码对于库的客户端是可见的,而更复杂的东西隐藏在二进制文件中(确保这不是一个主要原因,但是这对于熟悉软件库的人来说是显而易见的. ).并且任何不需要通过库的包含文件公开并且由其类私有使用的轻量级成员函数可以在类的.cpp文件中定义,而其声明/定义inline用于鼓励函数的内联状态(在这个特定情况下,不知道关键字是在两个地方还是只有一个).

Ben*_*igt 8

简而言之:将模板代码放在头文件中.如果优化程序无法做出有关内联的良好决策,请使用特定于编译器forceinlinenoinline关键字.


您可以而且应该将模板成员的定义放入头文件中.这可确保编译器在找到实际模板参数时可以在使用点访问定义,并且能够执行隐式实例化.

inline关键字对模板的影响非常小,因为模板的功能已经从单一的定义要求豁免(就是定义规则还要求所有的定义是相同的).这是编译器应该内联函数的提示.你可以省略它作为编译器不暗示函数的提示.所以以这种方式使用它.但优化器仍会查看其他因素(函数大小)并在内联时自行选择.

有些编译器有特殊的关键字,比如__attribute__(always_inline)__declspec(noinline)覆盖优化器的选择.

但是,大多数情况下,编译器足够聪明,不能内联"使函数调用更有意义的复杂代码".你不应该担心它,只需让优化器做它的事情.

便携式内联控制并不是有益的,因为内联的权衡取决于特定于平台.优化器应该已经知道那些特定于平台的权衡,如果您确实需要覆盖编译器的选择,请在每个平台的基础上执行此操作.


Dan*_*ien 5

1.鉴于我最近了解到,它将被编译器内联自动限定,无论它是否标记为内联,因此使用内联标记模板函数的定义是否有意义?或不?行为是否特定于编译器?

我认为你指的是在其类定义中定义的成员函数始终是内联函数.这符合C++标准,自第一次出版以来:

9.3会员职能

...

可以在其类定义中定义成员函数(8.4),在这种情况下,它是内联成员函数(7.1.2)

因此,在以下示例中,template <typename FloatT> my_class<FloatT>::my_function()始终是内联函数:

template <typename FloatT>
class my_class
{
public:
    void my_function() // `inline` member function
    {
        //...
    }
};

template <>
class my_class<double> // specialization for doubles
{
public:
    void my_function() // `inline` member function
    {
        //...
    }
};
Run Code Online (Sandbox Code Playgroud)

但是,通过移动定义的my_function()外部定义template <typename FloatT> my_class<FloatT>,它不会自动成为内联函数:

template <typename FloatT>
class my_class
{
public:
    void my_function();
};

template <typename FloatT>
void my_class<FloatT>::my_function() // non-`inline` member function
{
    //...
}

template <>
void my_class<double>::my_function() // non-`inline` member function
{
    //...
}
Run Code Online (Sandbox Code Playgroud)

在后一个例子中,使用inline带有定义的说明符确实有意义(例如,它不是多余的):

template <typename FloatT>
inline void my_class<FloatT>::my_function() // `inline` member function
{
    //...
}

template <>
inline void my_class<double>::my_function() // `inline` member function
{
    //...
}
Run Code Online (Sandbox Code Playgroud)

2.最重要的是,因为我希望将模板类的所有成员函数的定义聚集在一个文件中,或者是.inl或.cpp(在.cpp的情况下使用显式实例化),最好仍然能够提示编译器(MSVC和GCC)应该内联哪些函数,哪些不应该内联,确定模板函数是否可以实现,如何实现这一点,或者如果真的没有办法(我希望有),最佳折衷方案是什么?

如您所知,编译器可以选择内联函数,无论它是否具有说明inline符; 说明inline符只是一个提示.

没有标准的方法强制内联或阻止内联; 但是,大多数C++编译器都支持语法扩展来实现这一点.MSVC支持__forceinline关键字强制内联并#pragma auto_inline(off)防止它.G ++分别支持强制和防止内联的支持always_inlinenoinline属性.您应该参考编译器的文档以获取详细信息,包括如何在编译器无法按要求内联函数时启用诊断.

如果您使用这些编译器扩展,那么您应该能够向编译器提示函数是否内联.

一般来说,我建议将所有"简单"成员函数定义聚集在一个文件中(通常是标题),我的意思是,如果成员函数不需要在定义所需#include#includes 集合之上的更多s 类/模板.有时,例如,成员函数定义将需要#include <algorithm>,但不太可能需要<algorithm>包含类定义才能定义.您的编译器可以跳过它不使用的函数定义,但是大量的#includes可以显着延长编译时间,并且您不太可能想要内联这些非"简单"函数.


3.如果编译器如此聪明,以至于他们可以更好地选择应该内联哪个函数以及应该调用哪个函数,并且能够进行链接时代码生成和链接时优化,这有效地允许他们查看.cpp -located函数定义在链接时决定其内联或调用的命运,那么一个好的解决方案可能只是将所有定义移动到相应的.cpp文件中?

如果将所有函数定义放入CPP文件中,那么您将主要依赖LTO进行所有函数内联.由于以下原因,这可能不是您想要的:

  1. 至少在MSVC的LTCG中,你放弃了强制内联的能力(见内联, __在线,__ forceinline .)
  2. 如果CPP文件链接到共享库,则与共享库链接的程序将无法从LTO内联库函数中受益.这是因为编译器中间语言(IL) - LTO的输入 - 已被丢弃,并且在DLL或SO中不可用.
  3. 如果Under The Hood:链接时代码生成仍然正确,则"无法优化对静态库中函数的调用".
  4. 链接器将执行所有内联,这可能比编译器在编译时执行某些内联要慢得多.
  5. 编译器的LTO实现可能存在导致它不内联某些函数的错误.
  6. 使用LTO可能会对使用您的库的项目施加某些限制.例如,根据Under The Hood:链接时代码生成,"预编译头和LTCG是不兼容的".在/ LTCG(链接时代码生成) MSDN页面有其他注意事项,如"/ LTCG不适用于用/增量使用".

如果保留有可能要被内联函数定义在头文件,那么你可以使用这两种编译器内联和LTO.另一方面,将所有函数定义移动到CPP文件中将限制编译器内联仅限于翻译单元内.