【问题标题】:Where are complete types (not) required?哪里需要完整类型(不)?
【发布时间】:2012-02-24 19:02:34
【问题描述】:

我最近惊讶地发现这段代码可以编译(至少在 gcc 和 MSVC++ 上):

template<typename T>
class A {
public:
    T getT() { return T(); }
};

class B : public A<B> { };

如果不是这样:

class A;

class B : public A { };

class A {
public:
    B getB() { return B(); }
};

在我看来,一个模板类可以将一个不完整的类型作为模板参数并具有一个通过调用其构造函数返回一个并且仍然编译的函数,这对我来说似乎很奇怪。那么究竟哪里需要完整的类型(或者如果列表会更短,哪些地方不需要)?

【问题讨论】:

  • 如果您不将模板视为代码,而是将其视为代码生成工具,这可能会有所帮助,并且只有实际代码才需要具有语义意义.
  • @KerrekSB 是的,但我不清楚何时 代码生成工具 实际生成了它的代码。不过我现在很清楚了。

标签: c++ incomplete-type


【解决方案1】:

以下是不需要完整类型的场景:

  • 将成员声明为指向不完整类型的指针或引用。
  • 声明接受/返回不完整类型的函数。
  • 定义接受/返回指向不完整类型的指针/引用的函数。
  • 作为模板类型参数。

基本上你可以在编译器不需要知道type 的内存布局的任何地方使用不完整类型。

至于允许模板类型参数为不完整类型,标准在14.3.1 模板类型参数

中明确说明

【讨论】:

  • 我相信 struct a; a * p;union b; b * p; 会编译得很好,你只是不想取消引用指针。但是,union c; c * p; 会导致编译错误。
  • 取消引用确实需要一个完整的类型,所以前两个是。但是,我没有看到第三个表达式和第二个表达式之间的区别,我错过了什么吗?
  • 看看这个SO question。另外,我在ideone 上进行了测试,并得到以下枚举错误。
  • @Als 我不确定“基本上你可以在编译器不需要知道类型的内存布局的任何地方使用不完整类型。”如果class B声明public: typedef int TInt;class A &lt; T &gt;不能使用typedef T::TInt TIntFromDerived;,当class B : public A&lt; B &gt;{...};。我不认为typdefB 的内存布局有任何关系,但它还没有定义。
  • 如果模板类型参数不完整,是否有任何特殊规则可以说明如何在模板中使用模板类型参数?
【解决方案2】:

由于两阶段模板解析,这就是 CRTP 的工作方式。模板成员函数在实例化之前不会被解析。

编辑:也许,措辞不是很精确。我的意思是当编译器看到 class B : public A&lt; B &gt; {...};,它经过A&lt; B &gt;,注意到有一个函数B get() {...},但不评估它的定义,直到函数的实际实例化,此时B必须是一个完整的类型。

编辑:我相信,标准第 14.6 节中涵盖了确切的规则(正如 Als 在他的回答中指出的那样)。它根据两阶段模板名称查找在编译过程中的不同时间处理dependentnon-dependent名称及其解析。然而,不幸的是,两阶段名称查找实现在不同编译器上可能与标准不同。相同的代码可能在 GCC 上编译,而在 MSVC++ 上可能不编译,反之亦然。更重要的是,看似相同的代码可能会被相同的编译器拒绝。在 MSVC++ 上,当基类使用指向派生类函数的指针作为其函数的默认参数时,我遇到了一个问题。它没有在 MSVC++ 下编译并在 GCC 下编译(正确)。但是,使用派生类构造函数作为使用两个编译器编译的默认参数,即使在 MSVC++ 上也是如此。去图吧。

【讨论】:

  • 类模板的成员函数在定义处被解析(这就是使用typenametemplate来表示成员是类型或模板的原因),但它们在使用前并未实例化。
  • 我猜,我想说的是在解析的第一阶段没有评估函数定义。
  • 对不起,函数的实际实例化是什么时候?什么时候调用?
  • @SethCarnegie 何时调用函数或其地址。
  • @SethCarnegie 例如,B tmp; 由于继承而隐式实例化A&lt; B &gt;,而B 仍在实例化中且不完整。编译器注意到A&lt; B &gt; 中的get(); 声明,但未评估其定义,A&lt; B &gt; 然后B 被实例化。如果稍后您调用tmp.get();,此时编译器将实例化get();,并要求B 是一个完整的类型(现在是)。如果你永远不会调用get();,编译器甚至不会为它生成代码。
【解决方案3】:

模板并不是真正的代码;这是一个模板,它描述了如何构建代码,一旦你填写了缺失的部分(类型参数)。因此,编译器在模板定义中比在实际代码定义中允许更多的余地。当您真正使用模板时,识别出的类型,编译器需要生成实际代码并且所有通常的规则都适用。

如果编译器不需要知道对象成员的大小或偏移量,则不需要完整定义。例如,定义一个类的指针或引用不需要其中任何一个。在您尝试使用指针或引用时,您将需要一个完整的定义。

【讨论】:

    猜你喜欢
    • 2012-02-22
    • 2018-09-12
    • 2011-01-26
    • 1970-01-01
    • 1970-01-01
    • 2011-07-13
    • 1970-01-01
    • 1970-01-01
    • 2016-10-06
    相关资源
    最近更新 更多