时间:2014.03.01
地点:基地
—————————————————————————————
一、定义
前置声明是指对类、函数或者模板进行声明,仅仅是声明,不包含相关具体的定义。在很多场合我们可以用前置声明来代替#include语句。而且对于普通的类来说,建议使用前置声明,而不是#include。
—————————————————————————————
二、#include问题
某个事物的存在必然是为了解决另外一个事物存在所带来的问题。当我们使用#include时,#include会导致编译器打开很多的文件并处理相关的内容,如果头文件被修改,还会导致多次重新编译。即#include具有很强的依赖性,而前置声明正是为解决这种依赖性的,从而提高程序性能。比如以下经典的循环依赖问题:
class MyClassA{MyClassB B:} class MyClassB{MyClassa A:}以上两个类相互依赖于对付,如此产生循环依赖,编译不会通过,因为我们在使用一个类之前必须定义该类。而前置声明的作用只是告诉编译器,是编译器知道那是一个类名,所以在上述情况下即便有前置也是不被允许的,,因为他使用了类。#include虽然可以解决这个问题,但在此处,却会产生循环依赖。
注:在未提供完整的类之前,不能定义该类的对象,也不能在内联成员函数中使用该类的对象。比如以下代码:
class MyClassA;//前置声明 class MyClassB{ public: void Method(){x->DoSomething();}//这里有错,此处用到类的成员函数我们需要类的定义 private: MyClassA *x; //这里没错,因为我们并不需要该类的完整定义 }; class MyClassA{ public: void DoSomething(){}; private: MyClassA *x; };
那么有什么办法解决这个问题呢,有两种方案,一是修改两个类的定义顺序,这是很显然也最符合常规的。二是将Method方法修改为非内联函数,如此就在类体中我们并没有要用到x对象的成员函数了。
class MyClassA;//前置声明
class MyClassB{
public:
void Method(){}//这里只声明
private:
MyClassA *x; //这里没错,定义的是个指针,它指向MyClassA类型对象,因为前置声明,已经知道是MyClassA是个类型了
};
class MyClassA{
public:
void DoSomething(){};
private:
MyClassA *x;
};
void MyClassB::method(){x->DoSomething();}//此时,我们的类MyClassA已经有了定义了,说到底,必须有了定义我们才可以用它
在Primer一书中这这样说道class Screen;如果有这样的前置声明,那么在该声明之后,定义之前,类Screen是不完整类型,编译器碰到到它,只知道它是一个类,不知道它具体包含什么成员。不完全类型不能用来定义该类型的对象,只能定义该类型的指针/引用(引用本身就是基于指针实现的),当然也可用于声明(而不是定义)使用该类型作为形参或返回类型的函数。
—————————————————————————————
三、总结
类的前置声明只是告诉编译器这是一个类型,但无法告知类型的大小,成员等具体内容。而头文件则一一告之,所以形成他们的用法区别。使用前置声明的好处是能大大减小类的大小。名人有言:Never #include a header when a forward declaration will suffice. 那么到底有哪些情况可以前置声明而哪些情况必须#include呢,总的来说,当需要知道一个类的大小或成员时必须知道它的定义,如此不能用前置声明的方式,比如继承,类型包含令一类型成员变量不能使用前置声明解决问题,而譬如指针,引用形式完全可用前置声明代替#include