gpt4 book ai didi

c++ - 跨文件的代码组织,这些文件必须处理模板功能和内联

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:58:26 25 4
gpt4 key购买 nike

我维护着一个庞大的模板类库,这些模板类根据floatdouble类型执行代数计算。许多类都有访问器方法(getter和setter)以及其他运行少量代码的函数,因此,当编译器找到其定义时,此类函数必须被限定为内联函数。相反,其他成员函数包含复杂的代码,因此最好调用而不是内联。

函数定义的很大一部分位于 header 中,实际上位于 header 包含的.inl文件中。但是,也有许多类的函数定义通过floatdouble的显式实例化而幸福地存在于.cpp文件中,这对于库来说是一件好事(here解释了为什么)。最后,有许多类的功能定义在.inl文件(访问器方法)和.cpp文件(构造函数,析构函数和繁重的计算)中被打乱,这使得它们都很难维护。

只有在我知道防止内联某些函数的可靠方法的情况下,我才会在.inl文件中使用所有类的实现;如果inline关键字可以强烈建议编译器内联某些函数,则将在.cpp文件中使用所有的类实现。它不是。我真的希望库中的所有函数定义都驻留在.cpp文件中,但是由于访问器方法在整个库中得到了广泛使用,因此我必须确保在引用而不是调用它们时都将它们内联。

因此,在这方面,我的问题是:

  • 考虑到我最近了解到here的事实,不管是否用inline进行了标记,编译器都会自动将其定义为内联,是否可以使用inline标记模板函数的定义是否有意义?或不?
  • 而且最重要的是,由于我想将模板类的所有成员函数的定义收集在一个文件中,因此它是.inl或.cpp(在.cpp情况下使用显式实例化),最好还是能够提示编译器(MSVC和GCC)哪些函数应该内联,哪些不应该内联,请确保模板函数是否可以执行此操作,如何实现此目标,或者是否真的没有办法(我希望是),最佳折衷方案是什么?

  • ----------

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

    EDIT2:我确实知道。我喜欢向编译器提出建议。

    EDIT3:我仍然知道。这不是问题所在。

    ----------

    鉴于一些新信息,还有第三个问题与第二个问题并存。

    3.如果现在编译器非常聪明,以至于他们可以更好地选择应该内联哪个函数,应该调用哪个函数,并且能够进行链接时代码生成和链接时优化,从而使他们可以有效地查看.cpp。位于链接时的函数定义,以决定其被内联或调用的命运,那么一个好的解决方案可能就是将所有定义都移动到各自的.cpp文件中?

    ----------

    那么结论是什么?

    首先,我感谢Daniel Trebbien和Jonathan Wakely提出的结构合理且有充分根据的答案。两者都赞成,但只能选择其中之一。但是,给出的答案都没有一个能为我提供可接受的解决方案,因此,所选择的答案恰好比对其他人做出最终决定的帮助要小得多,有关细节将在接下来的内容中向有兴趣的人解释。

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

    将所有成员函数定义都放在.cpp文件中将阻碍轻量函数的内联,同时释放链接时优化的一些问题,这由Daniel Trebbien针对MSVC(处于较早的开发阶段)和Jonathan Wakely针对GCC(在目前的发展阶段)。并且将所有函数定义都放在 header (或.inl文件)中,并没有超过将每个类的实现归类为.inl和.cpp文件的总和的好处,同时还具有该决定的附加副作用:这将确保库的客户端只能看到基本访问器方法的代码,而二进制文件中则隐藏了更多汁的内容(尽管这不是主要原因,但是对于熟悉软件库的任何人来说,这都是显而易见的)。而且,不需要在库的包含文件中公开并由其类私有(private)使用的任何轻量级成员函数,都可以在该类的.cpp文件中进行定义,而其声明/定义由 inline来加倍鼓励函数的内联状态(尚不知道在这种情况下关键字是应该放在两个位置还是仅放在一个位置)。

    最佳答案

    1. Does it make any sense to mark the definition of a template function with inline in view of the fact that, as I've recently learnt, it is going to be automatically qualified as inline by the compiler regardless of whether it's marked with inline or not? Is the behavior compiler-specific?


    我认为您指的是这样一个事实,即在其类定义中定义的成员函数始终是内联函数。这是根据C++标准编写的,自第一次发布以来就已经存在:

    9.3 Member functions

    ...

    A member function may be defined (8.4) in its class definition, in which case it is an inline member function (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
    {
    //...
    }
    };
    但是,通过将 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
    {
    //...
    }
    在后一个示例中,将 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
    {
    //...
    }

    2. And most importantly, since I would like to have the definitions of all the member functions of a template class gathered together in a single file, either it's .inl or .cpp (using explicit instantiation in case of .cpp), preferably still being able to hint the compiler (MSVC and GCC) which of the functions should be inlined and which shouldn't, sure if such thing is possible with template functions, how can I achieve this or, if there is really no way (I hope there is), what would be the most optimal compromise?


    如您所知,编译器可以选择内联函数,无论它是否具有 inline说明符; inline说明符只是一个提示。
    没有强制内联或防止内联的标准方法。但是,大多数C++编译器都支持语法扩展以实现此目的。 MSVC支持 __forceinline 关键字来强制内联,而 #pragma auto_inline(off)则阻止内联。 G++支持分别强制和防止内联的 always_inline noinline属性。您应引用编译器的文档以获取详细信息,包括在编译器无法按要求内联函数时如何启用诊断。
    如果使用这些编译器扩展,则应该能够向编译器提示函数是否内联。
    通常,我建议将所有“简单”成员函数定义集中在一个文件中(通常是 header ),这意味着如果成员函数不需要比所需的 #include集合多很多的 #include,定义类/模板。例如,有时成员函数定义将需要 #include <algorithm>,但是类定义不太可能需要包含 <algorithm>才能进行定义。您的编译器可以跳过不使用的函数定义,但是更多的 #include可以显着延长编译时间,并且您不太可能希望内联这些非“简单”的函数。

    3. If compilers are so smart these days that they can make better choices about which function should be inlined and which should be called and are capable of link-time code generation and link-time optimization, which effectively allows them looking into a .cpp-located function definition at link time to decide its fate about being inlined or called, then maybe a good solution would be simply moving all the definitions into respective .cpp files?


    如果将所有功能定义都放在CPP文件中,那么几乎所有功能内联都将依赖LTO。由于以下原因,这可能不是您想要的:
  • 至少对于MSVC的LTCG,您放弃了强制内联的功能(请参阅inline, __inline, __forceinline。)
  • 如果CPP文件链接到共享库,则与共享库链接的程序将无法从库函数的LTO内联中受益。这是因为编译器中间语言(IL)(LTO的输入)已被丢弃,并且在DLL或SO中不可用。
  • 如果Under The Hood: Link-time Code Generation仍然正确,则“无法优化对静态库中函数的调用”。
  • 链接器将执行所有内联,这可能比让编译器在编译时执行一些内联​​要慢得多。
  • 编译器的LTO实现可能存在导致其未内联某些功能的错误。
  • 使用LTO可能会对使用您的库的项目施加某些限制。例如,根据Under The Hood: Link-time Code Generation,“预编译的 header 和LTCG不兼容”。 /LTCG (Link-time Code Generation) MSDN页面上还有其他注释,例如“/LTCG不适用于/INCREMENTAL”。

  • 如果将可能要内联的函数定义保留在头文件中,则可以同时使用编译器内联和LTO。另一方面,将所有函数定义移入CPP文件将限制编译器内联仅在转换单元内。

    关于c++ - 跨文件的代码组织,这些文件必须处理模板功能和内联,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11420802/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com