如何最好地从模板混乱切换到干净的类架构(C ++)?

时间:2022-12-21 17:00:36

Assuming a largish template library with around 100 files containing around 100 templates with overall more than 200,000 lines of code. Some of the templates use multiple inheritance to make the usage of the library itself rather simple (i.e. inherit from some base templates and only having to implement certain business rules).

假设一个较大的模板库包含大约100个文件,其中包含大约100个模板,总共超过200,000行代码。一些模板使用多重继承来使库本身的使用相当简单(即从一些基本模板继承并且只需要实现某些业务规则)。

All that exists (grown over several years), "works" and is used for projects.

所有存在的(长达数年),“工作”并用于项目。

However, compilation of projects using that library consumes a growing amount of time and it takes quite some time to locate the source for certain bugs. Fixing often causes unexpected side effects or is quite difficult, because some interdependent templates need changing. Testing is nearly impossible due to the sheer amount of functions.

但是,使用该库编译项目会占用越来越多的时间,并且需要相当长的时间来查找某些错误的源代码。修复通常会导致意外的副作用,或者非常困难,因为某些相互依赖的模板需要更改。由于功能的庞大,测试几乎是不可能的。

Now, I would really like to simplify the architecture to use less templates and more specialized smaller classes.

现在,我真的想简化架构以使用更少的模板和更专业的小类。

Is there any proven way to go about that task? What would be a good place to start?

有没有经过验证的方法来完成这项任务?什么是一个好的开始?

8 个解决方案

#1


13  

I'm not sure I see how/why templates are the problem, and why plain non-templated classes would be an improvement. Wouldn't that just mean even more classes, less type safety and so larger potential for bugs?

我不确定我是怎么看/为什么模板是问题,为什么简单的非模板化的类会是一个改进。这不仅仅意味着更多的课程,更少的类型安全性以及更大的漏洞潜力吗?

I can understand simplifying the architecture, refactoring and removing dependencies between the various classes and templates, but automatically assuming that "fewer templates will make the architecture better" is flawed imo.

我可以理解简化架构,重构和删除各种类和模板之间的依赖关系,但自动假设“更少的模板将使架构更好”是有缺陷的imo。

I'd say that templates potentially allow you to build a much cleaner architecture than you'd get without them. Simply because you can make separate classes totally independent. Without templates, classes functions which call into another class must know about the class, or an interface it inherits, in advance. With templates, this coupling isn't necessary.

我会说模板可能会让你构建一个比没有它们时更清晰的架构。仅仅因为你可以让独立的课程完全独立。如果没有模板,调用另一个类的类函数必须事先知道该类或它继承的接口。使用模板,这种耦合不是必需的。

Removing templates would only lead to more dependencies, not fewer. The added type-safety of templates can be used to detect a lot of bugs at compile-time (Sprinkle your code liberally with static_assert's for this purpose)

删除模板只会导致更多依赖,而不是更少。增加的模板类型安全性可用于在编译时检测大量错误(为此目的使用static_assert*地使用代码)

Of course, the added compile-time may be a valid reason to avoid templates in some cases, and if you only have a bunch of Java programmers, who are used to thinking in "traditional" OOP terms, templates might confuse them, which can be another valid reason to avoid templates.

当然,增加的编译时间可能是在某些情况下避免模板的正当理由,如果你只有一群Java程序员,他们习惯于用“传统的”OOP术语思考,那么模板可能会混淆他们,是避免模板的另一个有效理由。

But from an architecture point of view, I think avoiding templates is a step in the wrong direction.

但从架构的角度来看,我认为避免模板是朝着错误方向迈出的一步。

Refactor the application, sure, it sounds like that's needed. But don't throw away one of the most useful tools for producing extensible and robust code just because the original version of the app misused it. Especially if you're already concerned with the amount of code, removing templates will most likely lead to more lines of code.

重申应用程序,当然,这听起来像是需要的。但是,不要因为应用程序的原始版本滥用它而丢弃一个最有用的工具来生成可扩展和健壮的代码。特别是如果您已经关注代码量,删除模板很可能会导致更多的代码行。

#2


7  

You need automated tests, that way in ten years time when your succesor has the same problem he can refactor the code (probably to add more templates because he thinks it will simplify usage of the library) and know it still meets all test cases. Similarly the side effects of any minor bug fixes will be immediately visible (assuming your test cases are good).

您需要自动化测试,十年后,当您的成功者遇到同样的问题时,他可以重构代码(可能会添加更多模板,因为他认为这将简化库的使用)并且知道它仍然符合所有测试用例。同样,任何小错误修复的副作用将立即可见(假设您的测试用例很好)。

Other than that, "divide and conqueor"

除此之外,“划分和征服”

#3


3  

Write unit tests.

写单元测试。

Where the new code must do the same as the old code.

新代码必须与旧代码相同。

That's one tip at least.

这至少是一个提示。

Edit:

If you deprecate old code that you have replaced with the new functionality you can phase over to the new code little by little.

如果您弃用已替换为新功能的旧代码,则可以逐步逐步转换为新代码。

#4


2  

Well, the problem is that template way of thinking is very different from object-oriented inheritance-based way. It's hard to answer anything else than "redesign the whole thing and start from scratch".

嗯,问题是模板思维方式与面向对象继承的方式有很大不同。除了“重新设计整个事物并从头开始”之外,很难回答其他任何问题。

Of course, there may be a simple way for a particular case. We can't tell without knowing more about what you have.

当然,对于特定情况可能有一种简单的方法。如果不了解更多关于你拥有的东西,我们无法分辨。

The fact that the template solution is so difficult to maintain is an indication of a poor design anyway.

模板解决方案难以维护的事实无论如何都表明设计不佳。

#5


2  

Some points (but note: these are not evil indeed. If you want to change to non-template code, though, this can help out):

一些要点(但请注意:这些确实不是邪恶的。但是,如果你想改为非模板代码,这可能会有所帮助):


Lookup your static interfaces. Where do templates depend on what functions exist? Where do they need typedefs?

查找静态接口。模板在哪里取决于存在哪些功能?他们在哪里需要typedef?

Put the common parts in an abstract base class. A good example is when you happen to stumble over the CRTP idiom. You can just replace it with an abstract base class having virtual functions.

将公共部分放在抽象基类中。一个很好的例子就是当你偶然发现CRTP习语时。您只需将其替换为具有虚函数的抽象基类即可。

Lookup integer lists. If you find your code uses integral lists like list<1, 3, 3, 1, 3>, you can replace them with std::vector, if all the codes using them can live with working with runtime values instead of constant expressions.

查找整数列表。如果您发现代码使用整数列表,如list <1,3,3,1,3>,则可以使用std :: vector替换它们,如果使用它们的所有代码都可以使用运行时值而不是常量表达式。

Lookup type traits. There is much code involved checking whether some typedef exists, or whether some method exists in typical templated code. Abstract baseclasses solve these two issues by using pure virtual methods, and by inheriting typedefs to the base. Often, typedefs are only needed to trigger hideous features like SFINAE, which would then be superfluous too.

查找类型特征。有很多代码涉及检查是否存在某些typedef,或者是否存在典型模板化代码中的某些方法。抽象基类通过使用纯虚方法和将typedef继承到基类来解决这两个问题。通常,只需触发typedef来触发像SFINAE这样的可怕特征,这也是多余的。

Lookup expression templates. If your code uses expression templates to avoid creating temporaries, you will have to eliminate them and use the traditional way of returning / passing temporaries to the operators involved.

查找表达式模板。如果您的代码使用表达式模板来避免创建临时表,则必须消除它们并使用传统方式将临时值返回/传递给相关的运算符。

Lookup function objects. If you find your code uses function objects, you can change them to use abstract base classes too, and have something like void run(); to call them (or if you want to keep using operator(), better so! It can be virtual too).

查找函数对象。如果您发现您的代码使用了函数对象,您可以将它们更改为使用抽象基类,并具有类似void run();要调用它们(或者如果你想继续使用operator(),那就更好了!它也可以是虚拟的)。

#6


1  

As I understand, you are most concerned with build times, and the maintainability of your library?

据我了解,您最关心的是构建时间和库的可维护性?

First, don't try to "fix" all at once.

首先,不要试图一次“修复”所有。

Second, understand what you fix. Template complexity is there often for a reason, e.g. to enforce certain use, and make the compiler help you not make a mistake. That reason might sometimes be taken to far, but throwing out 100 lines because "noone really knows what they do" shouldn't be taken lightly. Everything I suggest here can introduce really nasty bugs, you have been warned.

其次,了解你的修复方法。模板复杂性经常出于某种原因,例如强制执行某些使用,并使编译器帮助您不犯错误。这个原因有时可能会被采用,但抛出100行,因为“没有人真正知道他们做了什么”不应该掉以轻心。我在这里建议的一切都可以引入非常讨厌的错误,你已经被警告了。

Third, consider cheaper fixes first: e.g. faster machines or distributed build tools. At least, throw in all the RAM the boards will take, and throw out old disks. It does maike a difference. One drive for OS, one drive for build is a cheap mans RAID.

第三,首先考虑更便宜的修复:例如更快的机器或分布式构建工具。至少,扔掉电路板将占用的所有RAM,并丢弃旧磁盘。它确实有所不同。一个用于操作系统的驱动器,一个用于构建的驱动器是便宜的勒芒RAID。

Is the library well documented? That's your best chance at making it Look into tools such as doxygen that help you create such a documentation.

图书馆有详细记录吗?这是制作它的最好机会查看doxygen等工具,帮助您创建这样的文档。

All considered? OK, now some suggestions for the build times ;)

考虑过了吗?好的,现在有一些关于构建时间的建议;)


Understand the C++ build model: every .cpp is compiled individually. That means many .cpp files with many headers = huge build. This is NOT an advise to put everything into one .cpp file, though! However, one trick (!) that can speed up a build immensely is to create a single .cpp file that includes a bunch of .cpp files, and only feed that "master" file to the compiler. You can't do that blindly, though - you need to understand the types of errors this could introduce.

理解C ++构建模型:每个.cpp都是单独编译的。这意味着许多.cpp文件有很多header = huge build。但是,这并不是建议将所有内容放入一个.cpp文件中!但是,可以极大地加速构建的一个技巧(!)是创建一个包含一堆.cpp文件的.cpp文件,并且只将该“master”文件提供给编译器。但是,你不能盲目地这样做 - 你需要了解这可能引入的错误类型。

If you don't have one yet, get a separate build machine that you can remote into. You'll have to do a lot of almost-full builds to check if you broke some include. You will want to run this in another machine, that doesn't block you from working on something else. Long term, you'll need it for daily integration builds anyway ;)

如果您还没有,请获取可以远程进入的单独构建计算机。你将不得不做很多几乎完整的构建来检查你是否破坏了一些包含。您将希望在另一台机器上运行它,这不会阻止您处理其他事情。从长远来看,无论如何,你都需要它来进行日常集成;)

Use precompiled headers. (scales better with fast machines, see above)

使用预编译的标头。 (使用快速机器可以更好地扩展,见上文)

Check your header inclusion policy. While every file should be "independent" (i.e. include everything it needs to be included by someone else), don't include liberally. Unfortunately, I haven't yet found a tool to find unnecessary #incldue statements, but it might help to spend some time removing unused headers in "hotspot" files.

检查标头包含策略。虽然每个文件都应该是“独立的”(即包括其他人需要包含的所有内容),但不要*包含。不幸的是,我还没有找到一个工具来查找不必要的#incldue语句,但是花一些时间删除“hotspot”文件中未使用的标题可能会有所帮助。

Create and use forward declarations for the templates you use. Often, you can incldue a header with forwad declarations in many places, and use the full header only in a few specific ones. This can greatly help compile time. Check the <iosfwd> header how the standard library does that for i/o streams.

为您使用的模板创建和使用前向声明。通常,您可以在许多地方使用转发声明来添加标头,并且仅在几个特定的​​标头中使用完整标头。这可以极大地帮助编译时间。检查 标头标准库如何为i / o流执行此操作。

overloads for templates for few types: If you have a complex function template that is useful only for a very few types like this:

少数类型模板的重载:如果你有一个复杂的函数模板,它只对很少的类型有用:

// .h
template <typename FLOAT> // float or double only
FLOAT CalcIt(int len, FLOAT * values) { ... }

You can declare the overloads in the header, and move the template to the body:

您可以在标头中声明重载,并将模板移动到正文:

// .h
float CalcIt(int len, float * values);
double CalcIt(int len, double * values);

// .cpp
template <typename FLOAT> // float or double only
FLOAT CalcItT(int len, FLOAT * values) { ... }

float CalcIt(int len, float * values) { return CalcItT(len, values); }
double CalcIt(int len, double * values) { return CalcItT(len, values); }

this moves the lengthy template to a single compilation unit.
Unfortunately, this is only of limited use for classes.

这会将冗长的模板移动到单个编译单元。不幸的是,这仅限于课程。

Check if the PIMPL idiom can move code from the headers into .cpp files.

检查PIMPL惯用语是否可以将代码从头文件移动到.cpp文件中。

The general rule that hides behind that is separate the interface of your library from the implementation. Use comments, detail namesapces and separate .impl.h headers to mentally and physically isolate what should be known to the outside from how it is accomplished. This exposes the real value of your library (does it actually encapsulate complexity?), and gives you a chance to replace "easy targets" first.

隐藏在后面的一般规则是将库的接口与实现分开。使用注释,详细信息名称和单独的.impl.h标头来精神上和物理上隔离外部应该知道的内容和完成方式。这暴露了你的库的真正价值(它实际上是否封装了复杂性?),并让你有机会首先替换“简单目标”。


More specific advise - and how useful the one given is - depends largely on the actual library.

Good luck!

#7


0  

As mentioned, unit tests are a good idea. Indeed, rather than breaking your code by introducing "simple" changes that are likely to ripple out, just focus on creating a suite of tests, and fixing non-compliance with the tests. Have an activity to update the tests when bugs come to light.

如上所述,单元测试是个好主意。实际上,不是通过引入可能会出现问题的“简单”更改来破坏代码,而是专注于创建一组测试,并修复不符合测试的问题。当bug出现时,有一个活动来更新测试。

Beyond that, I would suggest upgrading your tools, if possible, to help with debugging template-related problems.

除此之外,如果可能,我建议升级您的工具,以帮助调试与模板相关的问题。

#8


0  

I've often come across legacy templates that were huge and required a lot of time and memory to instantiate, but didn't need to be. In those cases, the easiest way to cut out the fat was to take all of the code that didn't rely on any of the template arguments and hide it in separate functions defined in a normal translation unit. This also had the positive side-effect of triggering fewer recompiles when this code had to be slightly modified or documentation changed. It sounds rather obvious, but it's really surprising how often people write a class template and think that EVERYTHING it does has to be defined in the header, rather than just the code that needs the templated information.

我经常遇到庞大的遗留模板,需要大量的时间和内存来实例化,但不需要。在这些情况下,减少脂肪的最简单方法是获取所有不依赖于任何模板参数的代码,并将其隐藏在普通翻译单元中定义的单独函数中。当此代码必须稍作修改或文档更改时,这也会产生触发较少重新编译的积极副作用。这听起来相当明显,但是人们编写类模板的频率并且认为必须在标题中定义它所做的一切,而不仅仅是需要模板化信息的代码,这实在令人惊讶。

Another thing you might want to consider is how often you clean up the inheritance hierarchies by making the templates "mixin" style instead of aggregations of multiple inheritance. See how many places you can get away with making one of the template arguments the name of the base class that it should derive from (the way boost::enable_shared_from_this works). Of course this typically only works well if the constructors take no arguments, as you don't have to worry about initializing anything correctly.

您可能想要考虑的另一件事是,通过使模板“mixin”样式而不是多重继承的聚合来清理继承层次结构的频率。通过使其中一个模板参数成为它应该派生的基类的名称(boost :: enable_shared_from_this的工作方式),查看你可以逃脱多少个地方。当然,这通常只有在构造函数不带参数时才能正常工作,因为您不必担心正确初始化任何内容。

#1


13  

I'm not sure I see how/why templates are the problem, and why plain non-templated classes would be an improvement. Wouldn't that just mean even more classes, less type safety and so larger potential for bugs?

我不确定我是怎么看/为什么模板是问题,为什么简单的非模板化的类会是一个改进。这不仅仅意味着更多的课程,更少的类型安全性以及更大的漏洞潜力吗?

I can understand simplifying the architecture, refactoring and removing dependencies between the various classes and templates, but automatically assuming that "fewer templates will make the architecture better" is flawed imo.

我可以理解简化架构,重构和删除各种类和模板之间的依赖关系,但自动假设“更少的模板将使架构更好”是有缺陷的imo。

I'd say that templates potentially allow you to build a much cleaner architecture than you'd get without them. Simply because you can make separate classes totally independent. Without templates, classes functions which call into another class must know about the class, or an interface it inherits, in advance. With templates, this coupling isn't necessary.

我会说模板可能会让你构建一个比没有它们时更清晰的架构。仅仅因为你可以让独立的课程完全独立。如果没有模板,调用另一个类的类函数必须事先知道该类或它继承的接口。使用模板,这种耦合不是必需的。

Removing templates would only lead to more dependencies, not fewer. The added type-safety of templates can be used to detect a lot of bugs at compile-time (Sprinkle your code liberally with static_assert's for this purpose)

删除模板只会导致更多依赖,而不是更少。增加的模板类型安全性可用于在编译时检测大量错误(为此目的使用static_assert*地使用代码)

Of course, the added compile-time may be a valid reason to avoid templates in some cases, and if you only have a bunch of Java programmers, who are used to thinking in "traditional" OOP terms, templates might confuse them, which can be another valid reason to avoid templates.

当然,增加的编译时间可能是在某些情况下避免模板的正当理由,如果你只有一群Java程序员,他们习惯于用“传统的”OOP术语思考,那么模板可能会混淆他们,是避免模板的另一个有效理由。

But from an architecture point of view, I think avoiding templates is a step in the wrong direction.

但从架构的角度来看,我认为避免模板是朝着错误方向迈出的一步。

Refactor the application, sure, it sounds like that's needed. But don't throw away one of the most useful tools for producing extensible and robust code just because the original version of the app misused it. Especially if you're already concerned with the amount of code, removing templates will most likely lead to more lines of code.

重申应用程序,当然,这听起来像是需要的。但是,不要因为应用程序的原始版本滥用它而丢弃一个最有用的工具来生成可扩展和健壮的代码。特别是如果您已经关注代码量,删除模板很可能会导致更多的代码行。

#2


7  

You need automated tests, that way in ten years time when your succesor has the same problem he can refactor the code (probably to add more templates because he thinks it will simplify usage of the library) and know it still meets all test cases. Similarly the side effects of any minor bug fixes will be immediately visible (assuming your test cases are good).

您需要自动化测试,十年后,当您的成功者遇到同样的问题时,他可以重构代码(可能会添加更多模板,因为他认为这将简化库的使用)并且知道它仍然符合所有测试用例。同样,任何小错误修复的副作用将立即可见(假设您的测试用例很好)。

Other than that, "divide and conqueor"

除此之外,“划分和征服”

#3


3  

Write unit tests.

写单元测试。

Where the new code must do the same as the old code.

新代码必须与旧代码相同。

That's one tip at least.

这至少是一个提示。

Edit:

If you deprecate old code that you have replaced with the new functionality you can phase over to the new code little by little.

如果您弃用已替换为新功能的旧代码,则可以逐步逐步转换为新代码。

#4


2  

Well, the problem is that template way of thinking is very different from object-oriented inheritance-based way. It's hard to answer anything else than "redesign the whole thing and start from scratch".

嗯,问题是模板思维方式与面向对象继承的方式有很大不同。除了“重新设计整个事物并从头开始”之外,很难回答其他任何问题。

Of course, there may be a simple way for a particular case. We can't tell without knowing more about what you have.

当然,对于特定情况可能有一种简单的方法。如果不了解更多关于你拥有的东西,我们无法分辨。

The fact that the template solution is so difficult to maintain is an indication of a poor design anyway.

模板解决方案难以维护的事实无论如何都表明设计不佳。

#5


2  

Some points (but note: these are not evil indeed. If you want to change to non-template code, though, this can help out):

一些要点(但请注意:这些确实不是邪恶的。但是,如果你想改为非模板代码,这可能会有所帮助):


Lookup your static interfaces. Where do templates depend on what functions exist? Where do they need typedefs?

查找静态接口。模板在哪里取决于存在哪些功能?他们在哪里需要typedef?

Put the common parts in an abstract base class. A good example is when you happen to stumble over the CRTP idiom. You can just replace it with an abstract base class having virtual functions.

将公共部分放在抽象基类中。一个很好的例子就是当你偶然发现CRTP习语时。您只需将其替换为具有虚函数的抽象基类即可。

Lookup integer lists. If you find your code uses integral lists like list<1, 3, 3, 1, 3>, you can replace them with std::vector, if all the codes using them can live with working with runtime values instead of constant expressions.

查找整数列表。如果您发现代码使用整数列表,如list <1,3,3,1,3>,则可以使用std :: vector替换它们,如果使用它们的所有代码都可以使用运行时值而不是常量表达式。

Lookup type traits. There is much code involved checking whether some typedef exists, or whether some method exists in typical templated code. Abstract baseclasses solve these two issues by using pure virtual methods, and by inheriting typedefs to the base. Often, typedefs are only needed to trigger hideous features like SFINAE, which would then be superfluous too.

查找类型特征。有很多代码涉及检查是否存在某些typedef,或者是否存在典型模板化代码中的某些方法。抽象基类通过使用纯虚方法和将typedef继承到基类来解决这两个问题。通常,只需触发typedef来触发像SFINAE这样的可怕特征,这也是多余的。

Lookup expression templates. If your code uses expression templates to avoid creating temporaries, you will have to eliminate them and use the traditional way of returning / passing temporaries to the operators involved.

查找表达式模板。如果您的代码使用表达式模板来避免创建临时表,则必须消除它们并使用传统方式将临时值返回/传递给相关的运算符。

Lookup function objects. If you find your code uses function objects, you can change them to use abstract base classes too, and have something like void run(); to call them (or if you want to keep using operator(), better so! It can be virtual too).

查找函数对象。如果您发现您的代码使用了函数对象,您可以将它们更改为使用抽象基类,并具有类似void run();要调用它们(或者如果你想继续使用operator(),那就更好了!它也可以是虚拟的)。

#6


1  

As I understand, you are most concerned with build times, and the maintainability of your library?

据我了解,您最关心的是构建时间和库的可维护性?

First, don't try to "fix" all at once.

首先,不要试图一次“修复”所有。

Second, understand what you fix. Template complexity is there often for a reason, e.g. to enforce certain use, and make the compiler help you not make a mistake. That reason might sometimes be taken to far, but throwing out 100 lines because "noone really knows what they do" shouldn't be taken lightly. Everything I suggest here can introduce really nasty bugs, you have been warned.

其次,了解你的修复方法。模板复杂性经常出于某种原因,例如强制执行某些使用,并使编译器帮助您不犯错误。这个原因有时可能会被采用,但抛出100行,因为“没有人真正知道他们做了什么”不应该掉以轻心。我在这里建议的一切都可以引入非常讨厌的错误,你已经被警告了。

Third, consider cheaper fixes first: e.g. faster machines or distributed build tools. At least, throw in all the RAM the boards will take, and throw out old disks. It does maike a difference. One drive for OS, one drive for build is a cheap mans RAID.

第三,首先考虑更便宜的修复:例如更快的机器或分布式构建工具。至少,扔掉电路板将占用的所有RAM,并丢弃旧磁盘。它确实有所不同。一个用于操作系统的驱动器,一个用于构建的驱动器是便宜的勒芒RAID。

Is the library well documented? That's your best chance at making it Look into tools such as doxygen that help you create such a documentation.

图书馆有详细记录吗?这是制作它的最好机会查看doxygen等工具,帮助您创建这样的文档。

All considered? OK, now some suggestions for the build times ;)

考虑过了吗?好的,现在有一些关于构建时间的建议;)


Understand the C++ build model: every .cpp is compiled individually. That means many .cpp files with many headers = huge build. This is NOT an advise to put everything into one .cpp file, though! However, one trick (!) that can speed up a build immensely is to create a single .cpp file that includes a bunch of .cpp files, and only feed that "master" file to the compiler. You can't do that blindly, though - you need to understand the types of errors this could introduce.

理解C ++构建模型:每个.cpp都是单独编译的。这意味着许多.cpp文件有很多header = huge build。但是,这并不是建议将所有内容放入一个.cpp文件中!但是,可以极大地加速构建的一个技巧(!)是创建一个包含一堆.cpp文件的.cpp文件,并且只将该“master”文件提供给编译器。但是,你不能盲目地这样做 - 你需要了解这可能引入的错误类型。

If you don't have one yet, get a separate build machine that you can remote into. You'll have to do a lot of almost-full builds to check if you broke some include. You will want to run this in another machine, that doesn't block you from working on something else. Long term, you'll need it for daily integration builds anyway ;)

如果您还没有,请获取可以远程进入的单独构建计算机。你将不得不做很多几乎完整的构建来检查你是否破坏了一些包含。您将希望在另一台机器上运行它,这不会阻止您处理其他事情。从长远来看,无论如何,你都需要它来进行日常集成;)

Use precompiled headers. (scales better with fast machines, see above)

使用预编译的标头。 (使用快速机器可以更好地扩展,见上文)

Check your header inclusion policy. While every file should be "independent" (i.e. include everything it needs to be included by someone else), don't include liberally. Unfortunately, I haven't yet found a tool to find unnecessary #incldue statements, but it might help to spend some time removing unused headers in "hotspot" files.

检查标头包含策略。虽然每个文件都应该是“独立的”(即包括其他人需要包含的所有内容),但不要*包含。不幸的是,我还没有找到一个工具来查找不必要的#incldue语句,但是花一些时间删除“hotspot”文件中未使用的标题可能会有所帮助。

Create and use forward declarations for the templates you use. Often, you can incldue a header with forwad declarations in many places, and use the full header only in a few specific ones. This can greatly help compile time. Check the <iosfwd> header how the standard library does that for i/o streams.

为您使用的模板创建和使用前向声明。通常,您可以在许多地方使用转发声明来添加标头,并且仅在几个特定的​​标头中使用完整标头。这可以极大地帮助编译时间。检查 标头标准库如何为i / o流执行此操作。

overloads for templates for few types: If you have a complex function template that is useful only for a very few types like this:

少数类型模板的重载:如果你有一个复杂的函数模板,它只对很少的类型有用:

// .h
template <typename FLOAT> // float or double only
FLOAT CalcIt(int len, FLOAT * values) { ... }

You can declare the overloads in the header, and move the template to the body:

您可以在标头中声明重载,并将模板移动到正文:

// .h
float CalcIt(int len, float * values);
double CalcIt(int len, double * values);

// .cpp
template <typename FLOAT> // float or double only
FLOAT CalcItT(int len, FLOAT * values) { ... }

float CalcIt(int len, float * values) { return CalcItT(len, values); }
double CalcIt(int len, double * values) { return CalcItT(len, values); }

this moves the lengthy template to a single compilation unit.
Unfortunately, this is only of limited use for classes.

这会将冗长的模板移动到单个编译单元。不幸的是,这仅限于课程。

Check if the PIMPL idiom can move code from the headers into .cpp files.

检查PIMPL惯用语是否可以将代码从头文件移动到.cpp文件中。

The general rule that hides behind that is separate the interface of your library from the implementation. Use comments, detail namesapces and separate .impl.h headers to mentally and physically isolate what should be known to the outside from how it is accomplished. This exposes the real value of your library (does it actually encapsulate complexity?), and gives you a chance to replace "easy targets" first.

隐藏在后面的一般规则是将库的接口与实现分开。使用注释,详细信息名称和单独的.impl.h标头来精神上和物理上隔离外部应该知道的内容和完成方式。这暴露了你的库的真正价值(它实际上是否封装了复杂性?),并让你有机会首先替换“简单目标”。


More specific advise - and how useful the one given is - depends largely on the actual library.

Good luck!

#7


0  

As mentioned, unit tests are a good idea. Indeed, rather than breaking your code by introducing "simple" changes that are likely to ripple out, just focus on creating a suite of tests, and fixing non-compliance with the tests. Have an activity to update the tests when bugs come to light.

如上所述,单元测试是个好主意。实际上,不是通过引入可能会出现问题的“简单”更改来破坏代码,而是专注于创建一组测试,并修复不符合测试的问题。当bug出现时,有一个活动来更新测试。

Beyond that, I would suggest upgrading your tools, if possible, to help with debugging template-related problems.

除此之外,如果可能,我建议升级您的工具,以帮助调试与模板相关的问题。

#8


0  

I've often come across legacy templates that were huge and required a lot of time and memory to instantiate, but didn't need to be. In those cases, the easiest way to cut out the fat was to take all of the code that didn't rely on any of the template arguments and hide it in separate functions defined in a normal translation unit. This also had the positive side-effect of triggering fewer recompiles when this code had to be slightly modified or documentation changed. It sounds rather obvious, but it's really surprising how often people write a class template and think that EVERYTHING it does has to be defined in the header, rather than just the code that needs the templated information.

我经常遇到庞大的遗留模板,需要大量的时间和内存来实例化,但不需要。在这些情况下,减少脂肪的最简单方法是获取所有不依赖于任何模板参数的代码,并将其隐藏在普通翻译单元中定义的单独函数中。当此代码必须稍作修改或文档更改时,这也会产生触发较少重新编译的积极副作用。这听起来相当明显,但是人们编写类模板的频率并且认为必须在标题中定义它所做的一切,而不仅仅是需要模板化信息的代码,这实在令人惊讶。

Another thing you might want to consider is how often you clean up the inheritance hierarchies by making the templates "mixin" style instead of aggregations of multiple inheritance. See how many places you can get away with making one of the template arguments the name of the base class that it should derive from (the way boost::enable_shared_from_this works). Of course this typically only works well if the constructors take no arguments, as you don't have to worry about initializing anything correctly.

您可能想要考虑的另一件事是,通过使模板“mixin”样式而不是多重继承的聚合来清理继承层次结构的频率。通过使其中一个模板参数成为它应该派生的基类的名称(boost :: enable_shared_from_this的工作方式),查看你可以逃脱多少个地方。当然,这通常只有在构造函数不带参数时才能正常工作,因为您不必担心正确初始化任何内容。