【问题标题】:Why can't a forward declaration be used for a std::vector?为什么不能对 std::vector 使用前向声明?
【发布时间】:2010-09-07 10:21:12
【问题描述】:

如果我这样创建一个类:

// B.h
#ifndef _B_H_
#define _B_H_

class B
{
private:
    int x;
    int y;
};

#endif // _B_H_

并像这样使用它:

// main.cpp
#include <iostream>
#include <vector>

class B; // Forward declaration.

class A
{
public:
    A() {
        std::cout << v.size() << std::endl;
    }

private:
    std::vector<B> v;
};

int main()
{
    A a;
}

编译main.cpp 时编译器失败。现在我知道的解决方案是#include "B.h",但我很好奇它为什么会失败。在这件事上,g++cl 的错误信息都不是很有启发性。

【问题讨论】:

  • 注意你可以vector&lt;T&gt;传递给一个只有前向声明类型T的函数,如果你将它作为vector&lt;T&gt;&amp;传递(但不是vector&lt;T&gt;,因为这需要复制操作)
  • “编译器失败”...错误是什么?在哪一行?
  • 一个很好的澄清请求,@Antonio,但当我在将近 13 年前发布这个问题时,我担心细节会漏掉。
  • 我的猜测是错误出现在v.size() 行。老实说,就目前而言,这个问题最好删除。这为问题提供了更好的视角stackoverflow.com/questions/38898935/…

标签: c++ stl


【解决方案1】:

伙计,您正在使用不完整的类型实例化 std::vector。不要碰前向声明,只需将构造函数的定义移动到.cpp 文件中即可。

【讨论】:

    【解决方案2】:

    就像 fyzix 所说,您的前向声明不起作用的原因是您的内联构造函数。即使是空的构造函数也可能包含大量代码,例如非 POD 成员的构造。在您的情况下,您有一个要初始化的向量,如果不完全定义其模板类型,您就无法做到这一点。

    析构函数也是如此。向量需要模板类型定义来告诉在销毁它所持有的实例时要调用什么析构函数。

    要摆脱这个问题,不要内联构造函数和析构函数。在 B 完全定义后的某个地方单独定义它们。

    如需更多信息, http://www.chromium.org/developers/coding-style/cpp-dos-and-donts

    【讨论】:

      【解决方案3】:

      事实上,如果 A 的构造函数是在一个知道 B 类型的编译单元中实现的,那么您的示例将会构建。

      无论 T 是什么,std::vector 实例都有一个固定的大小,因为正如其他人之前所说,它只包含一个指向 T 的指针。但是向量的构造函数取决于具体类型。您的示例无法编译,因为 A() 尝试调用向量的 ctor,而在不知道 B 的情况下无法生成该 ctor。以下是可行的方法:

      A 的声明:

      // A.h
      #include <vector>
      
      class B; // Forward declaration.
      
      class A
      {
      public:
          A(); // only declare, don't implement here
      
      private:
          std::vector<B> v;
      };
      

      A的实现:

      // A.cpp
      #include "A.h"
      #include "B.h"
      
      A::A() // this implicitly calls vector<B>'s constructor
      {
          std::cout << v.size() << std::endl;
      }
      

      现在 A 的用户只需要知道 A,而不需要知道 B:

      // main.cpp
      #include "A.h"
      
      int main()
      {
          A a; // compiles OK
      }
      

      【讨论】:

      • 请注意,这适用于 GCC 和 Clang,但不适用于 Visual Studio。 VS2015 失败,错误为error C2036: 'B *': unknown size 原因尚不完全清楚,但可能是VS2015 比GCC/Clang 更早地对vector&lt;T&gt; 进行模板实例化。
      • 只是为了添加更多信息...... Visual Studio VS2017 现在像gcc 一样工作并且编译得很好。
      【解决方案4】:

      无论您是使用向量还是尝试实例化一个 B,这都无关紧要。实例化需要对象的完整定义。

      【讨论】:

        【解决方案5】:

        需要的不仅仅是 B 的大小。例如,现代编译器将有一些花哨的技巧来在可能的情况下使用 memcpy 来加速向量复制。这通常是通过部分专注于元素类型的 POD 来实现的。您无法从前向声明中判断 B 是否是 POD。

        【讨论】:

          【解决方案6】:

          不能使用前向声明的原因是因为 B 的大小是未知的。

          在您的示例中没有理由不能在 A.h 中包含 B.h,那么您真正想要解决什么问题?

          编辑:还有另一种方法可以解决这个问题:停止使用 C/C++!这是 1970 年代... ;)

          【讨论】:

          • 'stop using C/C++' 似乎不能解决原始问题。病人:“医生,我这样做的时候很痛”,医生:“好吧,那就不要那样做!”是否有另一种可以与 C++ 良好互操作的语言或工具,您会建议吗?
          【解决方案7】:

          编译器需要知道“B”有多大才能生成适当的布局信息。相反,如果你说std::vector&lt;B*&gt;,那么编译器不需要知道 B 有多大,因为它知道指针有多大。

          【讨论】:

          • 但据我所知,std::vector v 应该只包含一个指向 B* 的指针,这应该与定义:B* here 的效果相同
          • 它不仅仅是一个指向 B 的指针——至少,它需要创建一个 B 数组来存储到该指针中。而要创建数组,你需要知道 B 的大小。
          • @lzprgmr:确实,vector&lt;T&gt; 可能只包含一个指向 T 的指针,所以我不同意 Curt 的回答。 vector&lt;B&gt; 的布局可以在不知道B 的定义的情况下知道,但由于vector 是一个模板,它的所有成员函数都必须为每个模板参数实例化:你不能将它们的声明与它们的实现分开。由于其中一些成员函数需要T 的定义,所以它必须是一个完整的类型才能在vector 中使用。问题是由于内联定义的成员函数,而不是 vector&lt;B&gt; 的布局,具体取决于 B 的布局。
          • @LucTouraille:等等,这个答案不是完全错误吗?正如其他人所说,这里的问题是内联构造函数,而不是容器本身......
          • @Nemo:是的,我认为这是不正确的。完全有可能以不需要完整类型的方式实现容器,事实上,许多 Boost 容器可以使用不完整类型实例化而不会遇到麻烦。标准库的容器需要一个完整的类型,因为标准是这么说的,而标准这么说主要是出于历史原因,正如 here 所解释的那样。
          【解决方案8】:

          要实例化 A::v,编译器需要知道 B 的具体类型。

          如果您想尽量减少#included 包的数量以缩短编译时间,您可以做两件事,它们实际上是彼此的变体:

          1. 使用指向 B 的指针
          2. 对B使用轻量级proxy

          【讨论】:

            猜你喜欢
            • 2015-09-11
            • 1970-01-01
            • 2016-01-17
            • 2012-11-17
            • 2021-09-06
            • 1970-01-01
            • 2015-04-10
            • 1970-01-01
            • 2018-11-08
            相关资源
            最近更新 更多