19

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

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

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

    class B;
    
    class A : public B
    {
    
        // ...
    }
    
  2. STD 类的前向声明不起作用。

    namespace std
    {
        class string;
    }
    
    class A
    {
        string aStringToTest;
    }
    

我该如何解决这些问题?

4

6 回答 6

28

第一个你不能解决的问题。

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

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

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

在基类案例中没有多大帮助,因为您不会得到“是”关系。

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

其次(正如评论中指出的那样),std::stringstd::basic_string<char>. 所以你需要转发声明(然后使用)它,到那时事情变得非常模糊和难以阅读,这是另一种成本。是不是真的值得吗?

于 2008-12-23T20:16:46.010 回答
7

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

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

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

你不能用它来

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

(我忘记了吗?)

考虑到声明 anauto_ptr与声明原始指针不同,因为auto_ptr实例化将在指针超出范围时尝试删除指针,并且删除需要类型的完整声明。如果使用auto_ptrin 来保存前向声明的类型,则必须提供析构函数(即使为空)并在看到完整的类声明后定义它。

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

typedef basic_string<char> string; // aproximate

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

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

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 {
   // ...
};

最后,罗迪给你的建议是:尽可能多地前向声明,但假设必须包含一些东西。

于 2008-12-23T21:33:29.397 回答
3

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

前向声明 STL 类通常是不可取的,因为实现通常包括加速编译的显式模板实例化。

于 2008-12-23T20:15:56.667 回答
2

您正在努力解决实际上不是问题的事情。使用您需要的头文件,并在可能的情况下减少对它们的要求。但不要试图把它推向极端,因为你会失败。

在某些情况下,PIMPL 成语可能会对您有所帮助,但不是在这里。

于 2008-12-23T20:17:51.030 回答
1

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

对于 std 命名空间中的类,您必须包含正确的标头 - 在这种情况下为 <string> - 然后执行以下三件事之一:

  1. 完全限定类型:std::string aStringToTest

  2. 只为该类型添加 using 声明: using std::string;

  3. 为 std 命名空间添加 using 声明: using namespace std;

于 2008-12-23T20:20:45.877 回答
1

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

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

基类在被声明为另一个类的基类时必须被声明(而不是前向声明)。

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

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

于 2009-03-04T17:01:48.940 回答