【问题标题】:Why does compiler need complete type when reference is used?为什么使用引用时编译器需要完整类型?
【发布时间】:2013-12-10 08:59:21
【问题描述】:

如果我有这样的标题:

#include <vector>

class Data;
class A {
  // const Data& getData( int i ) const { return a[i]; }
  std::vector<Data> a;
};

编译器正常编译它,因为它不需要知道一点数据类型。

通过引用重新调整值不依赖于类实现,因此不需要任何类内部知识。 但是当我取消注释访问器编译器开始抱怨invalid use of incomplete type 'class Data'。为什么?

【问题讨论】:

  • 实际上,std::vector&lt;Data&gt; 确实需要完整的类型,即使代码可以编译。
  • 在VS2013中,没有报错,你用的是什么编译器?
  • @juanchopanza 真的吗?
  • @LuchianGrigore 是的,很遗憾。这是boost containers 背后的动机之一。

标签: c++ class


【解决方案1】:

因此,C++ 标准在这一点上并不是特别清楚。您遇到的问题是,当 Data 不是完整类型时,您是否可以实例化 vector&lt;Data&gt;

如果我们查看第 17.6.4.8 节第 2 段,我们会发现以下语句:

特别是在以下情况下效果是不确定的:

  • ...
  • 如果在实例化模板组件时将不完整类型 (3.9) 用作模板参数,除非该组件特别允许。

std::vector 没有明确允许模板参数的类型不完整,因此这在技术上是无效代码。


这意味着这是无效代码,即使您的编译器接受它。

class Data;
class A {
    std::vector<Data> a;
};

【讨论】:

  • 我不认为错误是关于 std::vector ,错误可以在没有标准向量的情况下重现。
  • @Raxvan:对不起,我不明白你的意思。你能详细说明一下吗?请注意,编译器接受的代码(当访问器被注释掉时)也是非法的,即使编译器接受它也是如此。
  • @sharth 认为你已经声明了 Data 类型,然后你有 const Data &amp; foo(){return *some_poiner_to_data}; 以下表达式将给出错误:foo(); 而写 const Data &amp; tmp = foo(); 是可以的..我认为这是问题是,是吗?
  • 这里的问题是你混淆了实例化和声明。实例化发生在调用类的构造函数时。当类型不影响底层容器的大小时(例如使用矢量,但也使用所有智能指针),您需要在构造函数调用之前定义类。在这里,隐式实例化很重要。
  • @MadScienceDreams 如果你指的是我的小例子,那么在表达式 foo(); 中没有调用构造函数但是错误仅在 Visual Studio 编译器中,clang 和 gcc 是理智的并且没有给出错误。
【解决方案2】:

感谢人们的反应,我更好地理解了这个问题。 当我们摆脱@​​987654321@时,它变得更加清晰:

class Data;
class A {
  const Data& getData( int i ) const { return a[i]; }
  Data* a;
};

返回i-数组a的第一个元素需要知道它在内存中的什么位置。对于这个需要知道sizeof(Data),不完整的类型是不可能的。

std::vector 应该有完全相同的问题。

【讨论】:

  • 是的,或多或少。还有一个更根本的问题,但这就是它的根源。
【解决方案3】:

因为知道存在类Data 不足以知道std::vector&lt;Data&gt;::operator[](size_t i) 应该做什么,甚至它是否存在。这是类模板本质的基础。

【讨论】:

  • std::vector::operator[](size_t i) 对于某些类型 T 不存在?例如?
  • @klm123:它可能会(至少对于所有兼容类型),但编译器在实例化模板并解析其参数之前不知道这一点。 std::vector&lt;MyType&gt; 很容易有一个完全没有成员的特化,虽然标准在标准容器的情况下制作了这个 UB(我假设),但这不会改变类模板的基本行为。
  • 那不应该取决于是否使用了operator[]。
  • @klm123:那为什么呢?这里没有对std::vector&lt;T&gt; 成员的其他引用。很明显,在没有任何成员引用的情况下,代码可以编译(尽管从技术上讲它仍然是非法的)。
  • 好的。我知道了。谢谢。
【解决方案4】:

它不需要声明中的完整类型。在调用向量构造函数和析构函数(或任何其他成员)之前,它需要一个完整的类型。这是需要定义结构或类的大小的时候。

在您的代码中,A 的构造函数和析构函数被声明为默认值,因此在头文件中定义。向量不能分配或释放它不知道大小的东西,所以它会出错。您可以通过在“A.h”中显式声明析构函数和构造函数来解决此问题,并确保在定义任何成员函数之前定义数据。

以下代码有效且有效。

//"A.h"
class Data; // forward declaration only
class A
{
public:
  A();
  ~A();
  const Data& getData( int i ) const;
  std::vector<Data> a;
};

//"A.cpp"
#include "A.h"
class Data {int i;}; // actual class here before the definition of A::A(),...

A::A(){}
A::~A(){}
Data& A::getData(int i) const {return a[i];}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-04
    • 1970-01-01
    • 1970-01-01
    • 2014-07-28
    • 1970-01-01
    相关资源
    最近更新 更多