struct X; // Forward declaration of X
void f1(X* px) {} // Legal: can always use a pointer
void f2(X& x) {} // Legal: can always use a reference
X f3(int); // Legal: return value in function prototype
void f4(X); // Legal: parameter in function prototype
void f5(X) {} // ILLEGAL: *definitions* require complete types
0
把自己放在编译器's position: when you forward declare a type, all the compiler knows is that this type exists; it knows nothing about its size, members, or methods. This is why it'中,称为不完整类型 . 因此,您不能使用该类型来声明成员或基类,因为编译器需要知道该类型的布局 .
template <typename T>
class Foo : X<T> {} // OK as long as X is defined before
// Foo is instantiated.
Foo<int> a1; // Compiler error.
template <typename T> struct X {};
Foo<int> a2; // OK since X is now defined.
用它来声明另一个类模板的成员:
template <typename T>
class Foo {
X<T> m; // OK as long as X is defined before
// Foo is instantiated.
};
Foo<int> a1; // Compiler error.
template <typename T> struct X {};
Foo<int> a2; // OK since X is now defined.
使用此类型定义函数模板或方法
template <typename T>
void f1(X<T> x) {} // OK if X is defined before calling f1
template <typename T>
X<T> f2(){return X<T>(); } // OK if X is defined before calling f2
void test1()
{
f1(X<int>()); // Compiler error
f2<int>(); // Compiler error
}
template <typename T> struct X {};
void test2()
{
f1(X<int>()); // OK since X is defined now
f2<int>(); // OK since X is defined now
}
12 回答
只要您不需要定义(思考指针和引用),您就可以使用前向声明 . 这就是为什么大多数情况下你会在 Headers 中看到它们,而实现文件通常会拉出适当定义的 Headers .
主要规则是,您只能转发声明其内存布局(以及成员函数和数据成员)不需要在您转发声明它的文件中知道的类 .
这将排除基类和除引用和指针使用的类之外的任何东西 .
认为前向声明将使您的代码编译(obj已创建) . 但是,除非找到定义,否则链接(exe创建)将不会成功 .
我遵循的一般规则是不包括任何头文件,除非我必须 . 因此,除非我将类的对象存储为我的类的成员变量,否则我将不会包含它,我将只使用前向声明 .
我只想添加一个重要的事情,你可以用Luc Touraille的答案中没有提到的转发课程 .
What you can do with an incomplete type:
定义接受/返回指向不完整类型的指针/引用的函数或方法,并将指针/引用转发给另一个函数 .
模块可以将前向声明的类的对象传递给另一个模块 .
在只使用Pointer或引用类的文件中 . 如果指针/引用,则不应调用任何成员/成员函数 .
与
class Foo;
//前向声明我们可以声明Foo *或Foo&类型的数据成员 .
我们可以使用Foo类型的参数和/或返回值声明(但不定义)函数 .
我们可以声明Foo类型的静态数据成员 . 这是因为静态数据成员是在类定义之外定义的 .
当您希望将其他类型(类)用作类的成员时,通常需要在类头文件中使用前向声明 . 您不能在头文件中使用前向声明的类方法,因为C还不知道该类的定义 . 这是你必须进入.cpp文件的逻辑,但是如果你使用模板函数,你应该将它们简化为只使用模板的部分并将该函数移动到 Headers 中 .
我把它写成一个单独的答案,而不仅仅是一个评论,因为我不同意Luc Touraille的答案,不是基于合法性而是强大的软件和误解的危险 .
具体来说,我对您希望您的界面用户必须知道的隐含 Contract 存在问题 .
如果您正在返回或接受引用类型,那么您只是说它们可以通过指针或引用,而这些指针或引用只能通过前向声明来知道 .
当你返回一个不完整的类型
X f2();
时,你说你的调用者 must 具有X的完整类型规范 . 他们需要它来在呼叫站点创建LHS或临时对象 .类似地,如果接受不完整类型,则调用者必须构造作为参数的对象 . 即使该对象作为函数中的另一个不完整类型返回,调用站点也需要完整声明 . 即:
我认为有一个重要的原则, Headers 应该提供足够的信息来使用它,而不需要其他 Headers 的依赖 . 这意味着当您使用它声明的任何函数时,标头应该能够包含在编译单元中而不会导致编译器错误 .
Except
如果此外部依赖项是 desired 行为 . 而不是使用条件编译,你可以有一个详细记录的要求,他们提供自己的标头声明X.这是使用#ifdefs的替代方法,可以是一个有用的方法来引入模拟或其他变体 .
重要的区别是一些模板技术,你明确地不希望它们实例化它们,所以提到的只是有人不会对我嗤之以鼻 .
除了指向不完整类型的指针和引用之外,您还可以声明指定参数和/或返回不完整类型值的函数原型 . 但是,除非是指针或引用,否则无法定义具有不完整的参数或返回类型的函数 .
例子:
把自己放在编译器's position: when you forward declare a type, all the compiler knows is that this type exists; it knows nothing about its size, members, or methods. This is why it'中,称为不完整类型 . 因此,您不能使用该类型来声明成员或基类,因为编译器需要知道该类型的布局 .
假设以下前瞻性声明 .
这是你能做什么,不能做什么的 .
What you can do with an incomplete type:
What you cannot do with an incomplete type:
对于模板,没有绝对的规则:是否可以使用不完整的类型作为模板参数取决于类型在模板 .
例如,
std::vector<T>
要求其参数为完整类型,而boost::container::vector<T>
则不需要 . 有时,只有在使用某些成员函数时才需要完整类型;例如this is the case for std::unique_ptr<T> .记录良好的模板应在其文档中指出其参数的所有要求,包括它们是否需要完整类型 .
Lakos区分了类的用法
仅限名称(前向声明就足够了)和
in-size(需要类定义) .
我从来没有见过它更简洁明了:)
到目前为止,没有一个答案描述何时可以使用类模板的前向声明 . 所以,在这里 .
可以将类模板转发声明为:
遵循accepted answer的结构,
这是你能做什么,不能做什么的 .
What you can do with an incomplete type:
What you cannot do with an incomplete type: