关于c ++:基类的前向声明

Forward Declaration of a Base Class

我正在尝试创建适当的头文件,其中不包含太多其他文件,以使其保持干净并加快编译时间。

在执行此操作时,我遇到了两个问题:

  • 基类的前向声明不起作用。

    1
    2
    3
    4
    5
    6
    7
    class B;

    class A : public B
    {

        // ...
    }
  • STD类的前向声明不起作用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    namespace std
    {
        class string;
    }

    class A
    {
        string aStringToTest;
    }
  • 我该如何解决这些问题?


    您无法解决的第一个问题。

    第二个问题与标准库类无关。这是因为您将类的实例声明为您自己的类的成员。

    这两个问题都是由于要求编译器必须能够从其定义中找出类的总大小而引起的。

    但是,即使编译器还没有完整的定义,它也可以计算出指向该类的指针的大小。因此,在这种情况下,可能的解决方案是在消费类中具有一个指针(或引用)成员。

    在基类情况下,没有太多帮助,因为您不会得到"是"关系。

    对于std::string之类的东西也不值得做。首先,它应该是一个方便的字符缓冲区包装器,以免您对如此简单的事情进行内存管理。如果您随后持有一个指向它的指针,只是为了避免包括标头,那么您可能将一个好主意推得太远。

    其次(如注释中所指出),std::stringstd::basic_string的typedef。因此,您需要向前声明(然后使用)它,到那时事物变得非常模糊且难以阅读,这是另一种成本。是不是真的值得吗?


    正如Earwicker之前回答的那样,在任何情况下都不能使用前向声明,因为编译器需要知道类的大小。

    您只能在一组操作中使用前向声明:

    • 声明将向前声明的类作为参数或返回它的函数
    • 声明成员指针或对正向声明的类的引用
    • 在类定义中声明前向声明类型的静态变量

    您不能使用它来

    • 声明给定类型的成员属性(编译器需要大小)
    • 定义或创建该类型的对象或将其删除
    • 调用类的任何静态或成员方法或访问任何成员或静态属性

    (我忘了吗?)

    请注意,声明auto_ptr与声明原始指针不同,因为auto_ptr实例化将在超出范围时尝试删除该指针,并且删除操作需要完整声明该类型。如果使用auto_ptr in来保存前向声明的类型,则必须提供一个析构函数(即使为空),并在看到完整的类声明后对其进行定义。

    还有其他一些细微之处。当您向前声明一个类时,您在告诉编译器它将是一个类。这意味着它不能是另一种类型的enumtypedef。这是您尝试转发声明std::string时遇到的问题,因为它是模板的特定实例的typedef:

    1
    typedef basic_string<char> string; // aproximate

    要转发声明字符串,您需要转发声明basic_string模板,然后创建typedef。问题是该标准没有说明basic_string模板采用的参数数量,只是说明如果采用多个参数,则其余参数必须具有默认类型,以便上面的表达式可以编译。这意味着没有标准方法可以向前声明模板。

    另一方面,如果您要转发声明一个非标准模板(即非STL),则只要知道参数数量就可以这样做:

    1
    2
    3
    4
    5
    6
    template <typename T, typename U> class Test; // correct
    //template <typename T> class Test; // incorrect even if U has a default type

    template <typename T, typename U = int> class Test {
       // ...
    };

    最后,罗迪(Roddy)向您提供的建议:向前声明尽可能多的声明,但是假设必须包括一些内容。


    您正在努力解决实际上不是问题的事情。使用所需的头文件,并在可能的地方减少它们的需求。但不要尝试将其发挥到极致,因为那样会失败。

    在某些情况下,PIMPL惯用语可能会为您提供帮助,但在这里没有帮助。


    在这两种情况下,编译器都需要知道类型的大小。因此,前瞻性声明是不够的。基类可以添加成员或需要虚拟表。字符串成员将需要增加类的大小,以存储STL字符串类的大小。

    通常不建议进行STL类的前向声明,因为实现通常包括显式的模板实例化,以加快编译速度。


    >似乎前向声明对基类和stl类没有用。

    更正...
    对于基类和对象成员,前向声明是INAPPROPRIATE。 (这不是"无用的",而是"不适用的"。)

    当一个基类被声明为另一个类的基类时,必须声明(不向前声明)。

    当一个对象成员被另一个类声明,作为参数或作为返回值时,必须声明(不向前声明)。注意:按引用或按指针没有此约束。

    更正...
    根据ISO 14882,STL类的前向声明是未定义的行为。
    http://www.gotw.ca/gotw/034.htm


    对于基类,您需要具有完整的类型定义,而不仅仅是声明。派生类型标头将需要#include其基类的标头。

    对于std名称空间中的类,您必须包括适当的标头(在这种情况下为),然后执行3件事之一:

  • 完全限定类型:std :: string
    aStringToTest

  • 放置一个use声明
    该类型:使用std :: string;

  • 放入using声明
    std名称空间:使用名称空间std;