1

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

所有存在的(经过几年的发展)、“工作”并用于项目。

但是,使用该库编译项目会消耗越来越多的时间,并且需要相当长的时间才能找到某些错误的源。修复通常会导致意想不到的副作用或相当困难,因为一些相互依赖的模板需要更改。由于功能数量众多,几乎不可能进行测试。

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

是否有任何行之有效的方法来完成这项任务?什么是开始的好地方?

4

8 回答 8

13

我不确定我是否了解模板如何/为什么会出现问题,以及为什么普通的非模板类会有所改进。这难道不是意味着更多的类、更少的类型安全以及更大的潜在错误吗?

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

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

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

当然,在某些情况下,增加的编译时间可能是避免使用模板的正当理由,如果您只有一群习惯于“传统”OOP 术语思考的 Java 程序员,模板可能会使他们感到困惑,这可以是避免使用模板的另一个正当理由。

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

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

于 2008-11-27T16:06:44.813 回答
7

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

除此之外,“分而治之”

于 2008-11-27T15:50:29.540 回答
3

编写单元测试。

新代码必须与旧代码相同的地方。

这至少是一个提示。

编辑:

如果您弃用已用新功能替换的旧代码,您可以逐步过渡到新代码。

于 2008-11-27T15:54:07.567 回答
2

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

当然,对于特定情况可能有一种简单的方法。如果不了解您拥有的更多信息,我们将无法判断。

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

于 2008-11-27T15:45:48.007 回答
2

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


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

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

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

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

查找表达式模板。如果您的代码使用表达式模板来避免创建临时变量,您将不得不消除它们并使用将临时变量返回/传递给相关操作员的传统方式。

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

于 2008-11-27T16:03:53.023 回答
1

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

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

其次,了解您要解决的问题。模板的复杂性通常是有原因的,例如强制某些用途,并使编译器帮助您不犯错误。这个原因有时可能会被认为很远,但是不应该掉以轻心,因为“没有人真正知道他们在做什么”而抛出 100 行。我在这里建议的所有内容都可能引入非常讨厌的错误,您已被警告过。

第三,首先考虑更便宜的修复:例如更快的机器或分布式构建工具。至少,把板子占用的所有内存都扔进去,扔掉旧磁盘。它确实有所作为。一个用于操作系统的驱动器,一个用于构建的驱动器是廉价的人 RAID。

图书馆是否有据可查?那是你制作它的最佳机会。查看诸如 doxygen 之类的工具来帮助你创建这样的文档。

都考虑了?好的,现在对构建时间提出一些建议;)


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

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

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

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

为您使用的模板创建和使用前向声明。通常,您可以在许多地方包含带有前向声明的标题,而仅在少数特定的地方使用完整的标题。这可以极大地帮助编译时间。检查<iosfwd>标头标准库如何为 i/o 流执行此操作。

少数类型的模板重载:如果您有一个仅对少数类型有用的复杂函数模板,如下所示:

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

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

// .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); }

这会将冗长的模板移动到单个编译单元。
不幸的是,这仅对类有有限的用途。

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

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


更具体的建议——以及给出的建议有多有用——很大程度上取决于实际的图书馆。

祝你好运!

于 2008-11-27T23:49:25.423 回答
0

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

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

于 2008-11-27T15:55:20.047 回答
0

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

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

于 2008-11-27T20:33:19.127 回答