题
我正在尝试创建适当的头文件,其中不包含太多其他文件,以保持它们干净并加快编译时间。
我在做这件事时遇到了两个问题:
基类的前向声明不起作用。
class B; class A : public B { // ... }
STD 类的前向声明不起作用。
namespace std { class string; } class A { string aStringToTest; }
我该如何解决这些问题?
解决方案
第一个问题你无法解决。
第二个问题与标准库类无关。这是因为您将该类的实例声明为您自己的类的成员。
这两个问题都是由于要求编译器必须能够从类的定义中找出类的总大小。
但是,编译器可以计算出指向类的指针的大小,即使它还没有完整的定义。因此,在这种情况下,一个可能的解决方案是在消费类中拥有一个指针(或引用)成员。
在基类情况下没有太大帮助,因为您不会得到“是”关系。
也不值得为类似的事情做 std::string
. 。首先,它应该是字符缓冲区的一个方便的包装器,以节省您对如此简单的事情进行内存管理。如果您随后持有指向它的指针,只是为了避免包含标头,那么您的好主意可能太过分了。
其次(正如评论中指出的), std::string
是一个类型定义 std::basic_string<char>
. 。因此,您需要转发声明(然后使用)它,到那时事情就会变得非常晦涩难懂,难以阅读,这是另一种成本。是不是真的值得吗?
其他提示
正如 Earwicker 之前回答的那样,在任何这些情况下都不能使用前向声明,因为编译器需要知道类的大小。
您只能在一组操作中使用前向声明:
- 声明将前向声明的类作为参数或返回它的函数
- 声明对前向声明类的成员指针或引用
- 在类定义中声明前向声明类型的静态变量
你不能用它来
- 声明给定类型的成员属性(编译器需要大小)
- 定义或创建该类型的对象或删除它
- 调用类的任何静态或成员方法或访问任何成员或静态属性
(我有忘记什么吗?)
考虑到声明 auto_ptr
与声明原始指针不同,因为 auto_ptr
当指针超出范围时,实例化将尝试删除该指针,并且删除需要完整的类型声明。如果您使用 auto_ptr
要保存前向声明的类型,您必须提供析构函数(即使为空)并在看到完整的类声明后定义它。
还有其他一些微妙之处。当您转发声明一个类时,您是在告诉编译器它将是一个类。这意味着它不能是一个 enum
或一个 typedef
变成另一种类型。这就是当您尝试转发声明时遇到的问题 std::string
, ,因为它是模板的特定实例化的 typedef:
typedef basic_string<char> string; // aproximate
要转发声明字符串,您需要转发声明 basic_string
模板,然后创建 typedef
. 。问题是该标准没有规定参数的数量 basic_string
template 采用,它只是声明如果它采用多个参数,则其余参数必须具有默认类型,以便上面的表达式可以编译。这意味着没有标准的方法来转发声明模板。
另一方面,如果您想转发声明一个非标准模板(即非 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 {
// ...
};
最后罗迪给你的建议是:尽可能多地向前声明,但假设必须包含某些内容。
你太努力地去解决一些实际上不是问题的事情。使用您需要的头文件,并在可能的情况下减少对它们的要求。但不要尝试走极端,因为你会失败。
在某些情况下,PIMPL 习惯用法可能对您有帮助,但在这里不行。
在这两种情况下,编译器都需要知道类型的大小。因此,前向声明是不够的。基类可以添加成员或需要虚拟表。字符串成员需要增加类的大小以存储 STL 字符串类的大小。
前向声明 STL 类通常是不可取的,因为实现通常包括加速编译的显式模板实例化。
对于基类,您需要具有完整的类型定义,而不仅仅是声明。派生类型标头需要 #include 其基类的标头。
对于 std 命名空间中的类,您必须包含正确的标头 - 在本例中为 <string> - 然后执行以下三件事之一:
完全限定类型:std :: string altringtotest
将使用该类型的使用声明放置:使用 std::string ;
放入std名称空间的使用声明:使用命名空间 std;
> 似乎前向声明对于基类和 stl 类没有用。
更正...对于基类和对象成员来说,前向声明是不合适的。(不是“无用”,而是“不适用”。)
当基类被声明为另一个类的基类时,必须声明(而不是前向声明)。
当对象成员被另一个类声明、或者作为参数、或者作为返回值时,必须声明(而不是前向声明)。笔记:通过引用或通过指针没有该约束。
更正...根据 ISO 14882,STL 类的前向声明是未定义的行为。http://www.gotw.ca/gotw/034.htm