【问题标题】:Default template parameters with forward declaration带有前向声明的默认模板参数
【发布时间】:2010-12-20 00:28:18
【问题描述】:

是否可以在不指定或不知道这些参数的情况下转发声明使用默认参数的类?

例如,我想在 Traits 类中声明一个boost::ptr_list< TYPE >,而不是将整个 Boost 库拖到包含该特征的每个文件中。我想申报 namespace boost { template<class T> class ptr_list< T >; },但这不起作用,因为它与真正的类声明不完全匹配:

template < class T,
    class CloneAllocator = heap_clone_allocator,
    class Allocator = std::allocator<void*>
    >
class ptr_list { ... };

我的选择是只接受它还是在我的特征类中指定boost::ptr_list&lt; TYPE, boost::heap_clone_allocator, std::allocator&lt;void*&gt;? (如果我使用后者,我还必须转发声明 boost::heap_clone_allocator 并包含 &lt;memory&gt;,我想。)

我浏览了 Stroustrup 的书 SO,以及互联网上的其他内容,但没有找到解决方案。通常人们担心不包含 STL,解决方案是“只包含 STL 头”。但是,Boost 是一个更庞大且编译器密集型的库,因此除非绝对必要,否则我宁愿不使用它。

【问题讨论】:

  • Boost 是一组主要只有标头的库。为什么不只使用您需要的那些组件(及其依赖项)?并不是说不涉及任何工作,但这就是我们通常所做的。
  • 我愿意 -- ptr_list 是我唯一需要的文件。唯一的问题是单个 Boost 标头包含许多其他标头。 makedepend 的一个简单应用表明,just ptr_list 至少包含 385 个文件:boost 的 config、detail、iterator、mpl、preprocessor、range、smart_ptr、type_traits 和 utility——用于命名主要模块。你肯定能理解我是如何避免在我不需要的时候加入的。
  • 难怪编译时间飞涨。是的,包含这么多文件可能很烦人。但我认为正确的问题是:有没有办法找出为什么包含这些文件?是否可以将这些包含的文件划分为仅包含必要的内容。似乎没有捷径可走。
  • 由于类声明是一个不可分割的组成部分,如果该类的任何函数在头文件中使用了某个不起眼的mpl特性,那么所有使用该类的代码都必须包含该mpl头文件。这很令人痛心。
  • 假设您主要关心的是减少编译时间,ccache old.nabble.com/Use-of-ccache-with-Boost-td25663689.html 可能会有用。

标签: c++ templates forward-declaration


【解决方案1】:

任何使用您的工具并前向声明 boost 内容的编译单元都需要包含 boost 标头,除非您有某些程序实际上不会使用工具的 boost 部分。

确实,通过前向声明,您可以避免包含此类程序的 boost 标头。但是对于那些实际使用 boost 部分的程序,您必须手动包含 boost 标头(或有一个 #ifdef)。

请记住,在未来的 Boost 版本中可能会添加更多默认模板参数。我建议不要这条路线。如果您的目标是加快编译时间,我会考虑使用#define 来指示是否应禁用使用该 boost 库的代码。这样可以避免前向声明的麻烦。

【讨论】:

  • 嗯,我的代码中确实有部分不使用 Boost 库,但确实包含了特征文件——这就是为什么这个问题并非完全无用的原因。是的,我知道更改模板参数的潜在问题。这就是为什么我不喜欢这两种选择。
  • 好的。因此,您使用前向声明,然后使用 boost 库的编译单元负责将其单独包含在内(通常您的标头将包含它使用的 boost 标头)。无论哪种方式都很烦人,但可以接受。显然,您可以将特征类拆分为两个不同名称的不同标题部分(即使使用 boost 的部分继承自无 boost 的部分)但这看起来很恶心,并且会使专业化更加烦人。
【解决方案2】:

除非相关库提供了自己的前向声明标头,否则我认为您不能使用默认参数前向声明模板。这是因为您无法重新指定默认参数(即使它们匹配... gcc 仍然会报告“错误:默认参数的重新定义”)。

因此,据我所知,解决方案是让库提供前向声明头 Foo_fwd.h:

#ifndef INCLUDED_Foo_fwd_h_
#define INCLUDED_Foo_fwd_h_
template<class T, class U=char> class Foo; // default U=char up here
#endif

然后 Foo.h 中的完整实现将是:

#ifndef INCLUDED_Foo_h_
#define INCLUDED_Foo_h_
#include "Foo_fwd.h"
template<class T, class U> class Foo { /*...*/ }; // note no U=char here
#endif

所以现在您的代码也可以使用 Foo_fwd.h ......但不幸的是,由于这种方法需要修改原始 Foo.h 以删除默认参数,因此无法扩展到第 3 方库。也许我们应该游说 C++0x 团队允许对默认模板参数进行等效的重新指定,比如 typedefs...?

【讨论】:

    【解决方案3】:

    这里也是同样的问题。但是使用 STL。

    如果我的标题之一使用例如。 std::vector 那么我必须包含整个标题。从这个时候开始,每次我都包含我的标题,即使我的源代码根本没有引用 std::vector,标题也会与我的标题一起包含在内。如果您在很多地方包含此标头,则意味着大量过度解析。

    所以我转发声明了 std::vector 并使用了 std::vector* ,但由于默认参数,我的代码不想编译。如果我将默认参数放在我的头文件中,那么由于默认参数尊重,编译器会拒绝编译 stl 头文件。

    在这种情况下,我要做的是创建自己的 Vector 类,该类适应 std::vector 并将每个方法调用转发给它。大概这样就可以解决问题了。

    【讨论】:

    • 在你描述的情况下,所有的间接都是绝对不值得的。在你的情况下,唯一的惩罚是加载几个文件的(非常小的——时间 gcc 的编译有和没有#include &lt;vector&gt;)编译时的惩罚。使用指向向量的指针或添加包装类会使您的生活复杂化,增加可能的运行时损失,并且由于模板的工作方式,甚至无法解决问题。
    • 我还没有为此编写任何代码,似乎我不应该...看来我需要将标题包含在我的标题中。我在另一个线程中读到,由于预处理器和模板的原因,在每次编译优化后,真正的大型 c++ 项目平均编译 30-60 分钟。这是 C++ 的失败不是吗?
    【解决方案4】:

    是的。可以在任何时间、任何地点指定默认模板参数,只要声明不相互冲突。它们最终通过各种声明合并在一起。

    即使这样也是合法的:

    template< class A, class B, class C = long >
    class X;
    
    template< class A, class B = int, class C >
    class X;
    
    template< class A = short, class B, class C >
    class X { };
    

    第 14.1/10 节中给出了一个类似的例子。根据那段,函数默认参数的行为类似。

    祝你好运,让前向声明能够自我表现,而不是对所有事情都吐槽!

    【讨论】:

    • 不确定段落是否发生了变化,但@Potatoswatter 描述的内容现在可以在 §17.1/14 的“模板参数”部分找到。