我正在寻找当我被允许在另一个类的头文件中做类的前向声明的定义:
我是否可以为基类,为作为成员持有的类,为通过引用传递给成员函数的类,等等这样做?
我正在寻找当我被允许在另一个类的头文件中做类的前向声明的定义:
我是否可以为基类,为作为成员持有的类,为通过引用传递给成员函数的类,等等这样做?
当前回答
Lakos区分类的使用
In-name-only(向前声明就足够了)和 In-size(为此需要类定义)。
我从来没见过比这更简洁的发音:)
其他回答
在文件中,只使用指向类的指针或引用。指针/引用不能调用任何成员/成员函数。
//前向声明
我们可以声明类型为Foo*或Foo&的数据成员。
我们可以声明(但不能定义)带有Foo类型的参数和/或返回值的函数。
可以声明类型为Foo的静态数据成员。这是因为静态数据成员定义在类定义之外。
到目前为止,没有一个答案描述了何时可以使用类模板的前向声明。所以,开始吧。
类模板可以被声明为:
template <typename> struct X;
按照公认答案的结构,
以下是你能做的和不能做的。
使用不完整类型可以做什么:
Declare a member to be a pointer or a reference to the incomplete type in another class template: template <typename T> class Foo { X<T>* ptr; X<T>& ref; }; Declare a member to be a pointer or a reference to one of its incomplete instantiations: class Foo { X<int>* ptr; X<int>& ref; }; Declare function templates or member function templates which accept/return incomplete types: template <typename T> void f1(X<T>); template <typename T> X<T> f2(); Declare functions or member functions which accept/return one of its incomplete instantiations: void f1(X<int>); X<int> f2(); Define function templates or member function templates which accept/return pointers/references to the incomplete type (but without using its members): template <typename T> void f3(X<T>*, X<T>&) {} template <typename T> X<T>& f4(X<T>& in) { return in; } template <typename T> X<T>* f5(X<T>* in) { return in; } Define functions or methods which accept/return pointers/references to one of its incomplete instantiations (but without using its members): void f3(X<int>*, X<int>&) {} X<int>& f4(X<int>& in) { return in; } X<int>* f5(X<int>* in) { return in; } Use it as a base class of another template class 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. Use it to declare a member of another class template: 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. Define function templates or methods using this type 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 }
不完整类型不能做的事情:
Use one of its instantiations as a base class class Foo : X<int> {} // compiler error! Use one of its instantiations to declare a member: class Foo { X<int> m; // compiler error! }; Define functions or methods using one of its instantiations void f1(X<int> x) {} // compiler error! X<int> f2() {return X<int>(); } // compiler error! Use the methods or fields of one of its instantiations, in fact trying to dereference a variable with incomplete type class Foo { X<int>* m; void method() { m->someMethod(); // compiler error! int i = m->someField; // compiler error! } }; Create explicit instantiations of the class template template struct X<int>;
我遵循的一般规则是除非必须,否则不包含任何头文件。所以除非我将一个类的对象存储为我的类的成员变量,否则我不会包括它,我只会使用前向声明。
我写这篇文章是作为一个单独的回答,而不仅仅是评论,因为我不同意Luc Touraille的回答,不是基于合法性,而是基于健壮的软件和误解的危险。
具体地说,我对您希望界面的用户必须知道的隐含契约有一个问题。
如果您返回或接受引用类型,那么您只是说它们可以传递一个指针或引用,而它们反过来可能只通过前向声明知道这个指针或引用。
当你返回一个不完整的类型X f2();那么你说你的调用者必须拥有x的完整类型规范,他们需要它来在调用站点创建LHS或临时对象。
类似地,如果接受不完整类型,则调用方必须已经构造了作为形参的对象。即使该对象作为函数的另一个不完整类型返回,调用站点也需要完整的声明。例如:
class X; // forward for two legal declarations
X returnsX();
void XAcceptor(X);
XAcepptor( returnsX() ); // X declaration needs to be known here
我认为有一个重要的原则,一个头应该提供足够的信息来使用它,而不依赖于其他头。这意味着头文件应该能够包含在编译单元中,而不会在使用它声明的任何函数时引起编译器错误。
除了
如果这种外部依赖是所期望的行为。不使用条件编译,您可以有一个良好的文档要求,要求它们提供自己的声明x的头文件。这是使用#ifdefs的另一种选择,可以是引入模拟或其他变体的有用方法。 重要的区别是一些模板技术,你不需要明确地实例化它们,提到这些只是为了避免有人对我刻薄。
正如Luc Touraille已经很好地解释了在哪里使用和不使用类的前向声明。
我再补充一下为什么我们需要用它。
我们应该尽可能使用Forward声明,以避免不必要的依赖注入。
由于#include头文件被添加到多个文件中,因此,如果我们将一个头文件添加到另一个头文件中,它将在源代码的各个部分添加不必要的依赖注入,这可以通过尽可能将#include头文件添加到.cpp文件中来避免,而不是添加到另一个头文件中,并在头文件.h文件中尽可能使用类前向声明来避免。