跨文件的代码组织,必须处理模板函数和内联 [英] Code organization across files that has to deal with template functions and inlining

查看:150
本文介绍了跨文件的代码组织,必须处理模板函数和内联的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



函数定义的实质部分位于头文件中, inl文件包含的标题。但是也有许多类的函数定义通过显式实例化 float double 愉快地生活在.cpp文件中。 ,这在图书馆的情况下是一件好事(这里解释了为什么)。最后,有相当多的类的函数定义被穿过.inl文件(访问器方法)和.cpp文件(构造函数,析构函数和重型计算),这使得它们都难以维护。



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



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


  1. 鉴于以下事实,定义了 inline 的模板函数:这里,它将被编译器自动限定为内联,而不管它是否标记为 inline 还是不是?


  2. 最重要的是 模板类的所有成员函数的定义集中在单个文件中,或者是.inl或者.cpp(在.cpp的情况下使用显式实例化),优选 em>能够提示编译器(MSVC和GCC)哪些函数应当内联,这不应该确定这样的事情是否可能与模板函数,如何实现


>

----------



EDIT1: code> inline 关键字只是建议编译器内联函数。



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



EDIT3:我还是知道。这不是什么问题。



----------



>鉴于一些新信息,还有第三个问题与第二个问题紧密相关。



3。如果编译器这么聪明,他们可以做出更好的选择,哪些功能应该内联,哪些应该被称为能够链接时间代码生成和链接时间优化,有效地允许他们在链接时查找一个.cpp定位的函数定义来决定它的内联或调用的命运,那么也许一个好的解决方案是简单地将所有的定义移动到相应的.cpp文件?



----------



那么结论是什么?



首先,我要感谢Daniel Trebbien和Jonathan Wakely的有条理和有根据的答案。 upvote两个,但只能选择一个。然而,没有一个给出的答案给我一个可以接受的解决方案,所以选择的答案恰好是一个帮助我比别人做出最后决定,其细节下面解释任何人有兴趣。 / p>

好吧,由于我一直在评价代码的性能,而不仅仅是维护和开发的方便程度,在我看来,最可接受的妥协是将每个模板类的所有访问器方法和其他轻量级成员函数移动到相应头文件包含的.inl文件中,尝试使用 inline 关键字标记这些函数为编译器提供一个好的提示(或者为inline强制使用一个关键字),并将其余的函数移动到相应的.cpp文件中。



拥有所有成员位于.cpp文件中的函数定义将阻碍轻量级函数的内联,同时释放链接时间优化的一些问题,如由Daniel Trebbien为MSVC(在较早的开发阶段)和由Jonathan Wakely确定的GCC(在其当前发展阶段)。并且具有位于头部(或.inl文件)中的所有函数定义不会超过将每个类的实现分类到.inl和.cpp文件并且结合该决定的奖金副作用的总体益处:它将确保只有原始访问器方法的代码对于库的客户端是可见的,而更多多汁的东西被隐藏在二进制文件中(确保这不是主要原因,但是对于熟悉软件库的任何人来说,这一点是显而易见的)。任何轻量级的成员函数不需要通过库的include文件暴露,并且由其类私有使用,它的定义可以在类的.cpp文件中,而其声明/定义用 inline 以鼓励函数的内联状态(不知道关键字是否应该在这两个地方,或者在这种特殊情况下只有一个)。

解决方案


&#x31 ;.鉴于以下事实,使用内联来标记模板函数的定义是有意义的,正如我最近所学到的,它将被编译器自动限定为内联,而不管它是否被标记为内联?是编译器特有的行为吗?


我认为你是指在其类定义中定义的成员函数总是 功能。这是根据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`成员函数
{
// ...
}
};

模板<>
class my_class< double> //双重特殊化
{
public:
void my_function()//`inline`成员函数
{
// ...
}
};但是,通过移动 my_function()


$ b c> 的定义之外的模板< typename FloatT> my_class< FloatT> ,它不会自动为内联功能:

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

template< typename FloatT>
void my_class< FloatT> :: my_function()//非`inline`成员函数
{
// ...
}

模板<>
void my_class< double> :: my_function()//非`inline`成员函数
{
// ...
}

在后面的例子中,使用 inline code>具有定义的说明符:

  template< typename FloatT> 
inline void my_class< FloatT> :: my_function()//`inline`成员函数
{
// ...
}

模板<>
inline void my_class< double> :: my_function()//`inline`成员函数
{
// ...
}







&#x32;最重要的是,因为我想有一个模板类的所有成员函数的定义集合在一个单一的文件,它的.inl或.cpp(在.cpp的情况下使用显式实例化),最好仍然能提示编译器(MSVC和GCC)哪些函数应该内联,哪些不应该,确保这样的事情是可能的模板函数,如何实现这个,或者如果真的没有办法(我希望有),什么是最佳的折衷?


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



没有标准方法强制内联或阻止内联;然而,大多数C ++编译器支持语法扩展来实现这一点。 MSVC支持 __ forceinline 关键字强制内联,并且 #pragma auto_inline(off)以阻止它。 G ++支持 always_inline noinline 属性分别强制和防止内联。您应该参考您的编译器文档,了解详细信息,包括如何在编译器无法根据请求内联函数时启用诊断。



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



一般来说,我建议将所有简单成员函数定义集合在一起单个文件(通常是头文件),我的意思是,如果成员函数不需要非常多的 #include #包括定义类/模板所需的。有时,例如,成员函数定义将需要 #include ,但是类定义不太可能需要< algorithm> 包含在内以便定义。你的编译器能够跳过它不使用的函数定义,但是更大数量的 #include 可以明显延长编译时间,你不会想要以符合这些非简单的功能。







&#x33;如果编译器现在这么聪明,他们可以做出更好的选择,哪些函数应该内联,哪些应该被调用,并且能够进行链接时间代码生成和链接时优化,这有效地允许他们查看.cpp位置链接时的函数定义来决定其内联或调用的命运,那么也许一个好的解决方案是将所有的定义移动到相应的.cpp文件中?


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


  1. 至少对于MSVC的LTCG,您放弃强制内联的能力内联,__inline,__forceinline 。)

  2. 如果CPP文件链接到共享库,则与共享库链接的程序不会受益于库函数的LTO内联。这是因为编译器中间语言(IL) - 对LTO的输入已被丢弃,并且在DLL或SO中不可用。

  3. 如果在Hood:链接时代码生成仍然正确,调用静态库中的函数不能优化。

  4. 链接器将执行所有内联,这可能比编译器在编译时执行一些内联​​要慢得多。

  5. 编译器的LTO实现可能有错误,导致它不能内联某些函数。

  6. 使用LTO可能会对使用库的项目施加一些限制。例如,根据底下:链接时代码生成, 预编译头和LTCG不兼容。 / LTCG(链接时代码生成) MSDN页面还有其他注释,例如/ LTCG不能与/ INCREMENTAL一起使用。

如果保持可能被内联函数定义,那么您可以使用两者编译器内联和LTO。另一方面,将所有函数定义移动到CPP文件中将限制编译器内联到仅在翻译单元内。


I'm maintaining a large library of template classes that perform algebraic computations based on either float or double type. Many of the classes have accessor methods (getters and setters) and other functions that run small amounts of code, therefore such functions need to be qualified as inline when the compiler locates their definitions. Other member functions, in contrast, contain sophisticated code and thus would better be called rather than inlined.

A substantial part of the function definitions are located in headers, actually in .inl files included by headers. But there are also many classes whose function definitions happily live in .cpp files by means of explicit instantiation for float and double, which is rather a good thing to do in case of a library (here explained why). And finally, there is a considerable number of classes whose function definitions are broken across .inl files (accessor methods) and .cpp files (constructors, destructors, and heavy computations), which makes them all pretty difficult to maintain.

I would have all my class implementations in .inl files only if I knew a reliable way to prevent some functions from being inlined, or in .cpp files if inline keyword could strongly suggest compiler to inline some of the functions, which, of course, it does not. I would really prefer all the function definitions in the library to reside in .cpp files, but since accessor methods are used extensively throughout the library, I have to make sure they are inlined whenever referenced, not called.

So, in this connection, my questions are:

  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 here, it is going to be automatically qualified as inline by the compiler regardless of whether it's marked with inline or not?

  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?

----------

EDIT1: I knew that inline keyword is just a suggestion to the compiler to inline a function.

EDIT2: I really do know. I like making suggestions to the compiler.

EDIT3: I still know. It's not what the question is about.

----------

In view of some new information, there is also third question that goes hand in hand with the second one.

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?

----------

So what's the conclusion?

First of all, I'm grateful to Daniel Trebbien and Jonathan Wakely for their structured and well-founded answers. Upvoted both but had to choose just one. None of the given answers, however, presented an acceptable solution to me, so the chosen answer happened to be the one that helped me slightly more than others in making the final decision, the details of which are explained next for anyone who's interested.

Well, since I've always been valuing the performance of code more than how much convenient it is to maintain and develop, it appears to me that the most acceptable compromise would be to move all the accessor methods and other lightweight member functions of each of the template classes into the .inl file included by the respective header, marking these functions with inline keyword in an attempt to provide the compiler with a good hint (or with a keyword for inline forcing), and move the rest of the functions into the respective .cpp file.

Having all member function definitions located in .cpp files would hinder inlining of lightweight functions while unleashing some problems with link-time optimization, as has been ascertained by Daniel Trebbien for MSVC (in an older stage of development) and by Jonathan Wakely for GCC (in its current stage of development). And having all function definitions located in headers (or .inl files) doesn't outweigh the summary benefit of having the implementation of each class sorted into .inl and .cpp files combined with a bonus side effect of this decision: it would ensure that only the code of primitive accessor methods is visible to a client of the library, while more juicy stuff is hidden in the binaries (ensuring this wasn't a major reason, however, but this plus was obvious for anyone who is familiar with software libraries). And any lightweight member function that doesn't need to be exposed by the include files of the library and is used privately by its class can have its definition in the .cpp file of the class, while its declaration/definition is spiced with inline to encourage the inline status of the function (don't know yet whether the keyword should be in both places or just one in this particular case).

解决方案

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?

I think you are referring to the fact that a member function defined in its class definition is always an inline function. This is per the C++ Standard, and has been since the first publication:

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)

So, in the following example, template <typename FloatT> my_class<FloatT>::my_function() is always an inline 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
    {
        //...
    }
};

However, by moving the definition of my_function() outside of the definition of template <typename FloatT> my_class<FloatT>, it is not automatically an inline function:

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
{
    //...
}

In the latter example, it does make sense (as in, it's not redundant) to use the inline specifier with the definitions:

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?

As you know, the compiler may elect to inline a function, whether or not it has the inline specifier; the inline specifier is just a hint.

There is no standard way to force inlining or prevent inlining; however, most C++ compilers support syntactic extensions for accomplishing just that. MSVC supports a __forceinline keyword to force inlining and #pragma auto_inline(off) to prevent it. G++ supports always_inline and noinline attributes for forcing and preventing inlining, respectively. You should refer to your compiler's documentation for details, including how to enable diagnostics when the compiler is unable to inline a function as requested.

If you use those compiler extensions, then you should be able to hint to the compiler whether a function is inlined or not.

In general, I recommend to have all "simple" member function definitions gathered together in a single file (usually the header), by which I mean, if the member function does not require very many more #includes above the set of #includes required to define the classes/templates. Sometimes, for example, a member function definition will require #include <algorithm>, but it is unlikely that the class definition requires <algorithm> to be included in order to be defined. Your compiler is able to skip over function definitions that it does not use, but the larger number of #includes can noticeably lengthen compile times, and it is unlikely that you will want to inline these non-"simple" functions anyway.


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?

If you place all of your function definitions into CPP files, then you will be relying on LTO for mostly all function inlining. This may not be what you want for the following reasons:

  1. At least with MSVC's LTCG, you give up the ability to force inlining (See inline, __inline, __forceinline.)
  2. If the CPP files are linked to a shared library, then programs linking with the shared libraries will not benefit from LTO inlining of library functions. This is because the compiler intermediate language (IL)—the input to LTO—has been discarded and is not available in the DLL or SO.
  3. If Under The Hood: Link-time Code Generation is still correct, "calls to functions in static libraries can't be optimized".
  4. The linker would be performing all inlining, which might be a lot slower than having the compiler perform some inlining at compile time.
  5. The compiler's LTO implementation might have bugs that cause it to not inline certain functions.
  6. Use of LTO might impose certain limitations on projects using your library. For example, according to Under The Hood: Link-time Code Generation, "precompiled headers and LTCG are incompatible". The /LTCG (Link-time Code Generation) MSDN page has other notes, such as "/LTCG is not valid for use with /INCREMENTAL".

If you keep the likely-to-be-inlined function definitions in the header files, then you could use both compiler inlining and LTO. On the other hand, moving all function definitions into CPP files will restrict compiler inlining to only within the translation units.

这篇关于跨文件的代码组织,必须处理模板函数和内联的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆