何时可使用前向声明?

我正在寻找什么时候容许在另外一个类的头文件中对一个类进行前向声明的定义: 函数

我是否能够针对基类,做为成员持有的类,经过引用传递给成员函数的类等进行此操做? spa


#1楼

Lakos区分类用法 指针

  1. 仅名称中 (对于此声明,前向声明已足够)和
  2. in-size (须要为其定义类)。

我从未见过它的发音更简洁:) code


#2楼

我将其做为一个单独的答案而不是仅仅做为评论,是由于我不一样意Luc Touraille的回答,这不是基于合法性,而是基于强大的软件和错误解释的危险。 对象

具体来讲,我对您的界面用户但愿了解的隐含合同有疑问。 get

若是您要返回或接受引用类型,那么您只是说它们能够经过指针或引用传递,而这些指针或引用又只能经过前向声明来知道。 编译器

当您返回不完整的类型时, X f2(); 那么您说您的呼叫者必须具备X的完整类型规范。他们须要它才能在呼叫站点建立LHS或临时对象。 io

一样,若是您接受不完整的类型,则调用者必须构造了做为参数的对象。 即便该对象做为另外一个不完整类型从函数中返回,调用站点也须要完整的声明。 即: 编译

class X;  // forward for two legal declarations 
X returnsX();
void XAcceptor(X);

XAcepptor( returnsX() );  // X declaration needs to be known here

我认为有一个重要的原则,即标头应提供足够的信息以使用它,而没必要依赖其余标头。 这意味着在使用标头声明的任何函数时,标头应该可以包含在编译单元中,而不会引发编译器错误。 模板

除了

  1. 若是外部的依赖是指望的行为。 与其使用条件编译,不如使用有据可查的要求,让它们提供本身的标头声明X。这是使用#ifdefs的替代方法,而且是引入模拟或其余变体的有用方法。

  2. 重要的区别是某些模板技术,其中明确不要求您实例化它们,只是提到了某人不会对我说鬼话。


#3楼

到目前为止,没有一个答案描述什么时候可使用类模板的前向声明。 因此,就到这里。

能够将类模板转发声明为:

template <typename> struct X;

按照接受的答案的结构,

这是您能够作的和不能作的。

使用不完整的类型能够作什么:

  • 声明一个成员是另外一个类模板中不完整类型的指针或引用:

    template <typename T> class Foo { X<T>* ptr; X<T>& ref; };
  • 声明一个成员为其不完整实例之一的指针或引用:

    class Foo { X<int>* ptr; X<int>& ref; };
  • 声明接受/返回不完整类型的函数模板或成员函数模板:

    template <typename T> void f1(X<T>); template <typename T> X<T> f2();
  • 声明接受或返回其不完整实例之一的函数或成员函数:

    void f1(X<int>); X<int> f2();
  • 定义接受/返回不完整类型的指针/引用的函数模板或成员函数模板(但不使用其成员):

    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; }
  • 定义接受/返回对其不完整实例之一的指针/引用的函数或方法(但不使用其成员):

    void f3(X<int>*, X<int>&) {} X<int>& f4(X<int>& in) { return in; } X<int>* f5(X<int>* in) { return in; }
  • 将其用做另外一个模板类的基类

    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 }

不完整类型不能作的事情:

  • 将其实例化之一用做基类

    class Foo : X<int> {} // compiler error!
  • 使用其实例化之一来声明一个成员:

    class Foo { X<int> m; // compiler error! };
  • 使用其实例化之一定义函数或方法

    void f1(X<int> x) {} // compiler error! X<int> f2() {return X<int>(); } // compiler error!
  • 使用其实例化之一的方法或字段,实际上试图取消引用具备不完整类型的变量

    class Foo { X<int>* m; void method() { m->someMethod(); // compiler error! int i = m->someField; // compiler error! } };
  • 建立类模板的显式实例化

    template struct X<int>;

#4楼

我只想添加一个重要的事情,您可使用Luc Touraille的答案中未提到的转发类来作。

使用不完整的类型能够作什么:

定义接受/返回不完整类型的指针/引用并将该指针/引用转发给另外一个函数的函数或方法。

void  f6(X*)       {}
void  f7(X&)       {}
void  f8(X* x_ptr, X& x_ref) { f6(x_ptr); f7(x_ref); }

一个模块能够经过一个前向声明的类的对象传递给另外一个模块。


#5楼

只要不须要定义(例如指针和引用),就能够避免使用前向声明。 这就是为何大多数状况下您会在标头中看到它们的缘由,而实现文件一般会提取相应定义的标头。

相关文章
相关标签/搜索