大连做网站哪里好,湖北手机网站制作,什么是网站开发中的分页,传媒公司名字大气一些转自#xff1a;http://blog.csdn.net/fjb2080/archive/2010/04/27/5533514.aspx 作者#xff1a;清林#xff0c;博客名#xff1a;飞空静渡 刚开始学习c的人都会遇到这样的问题#xff1a; 定义一个类 class A#xff0c;这个类里面使用了类B的对象b#xff0c;然后定… 转自http://blog.csdn.net/fjb2080/archive/2010/04/27/5533514.aspx 作者清林博客名飞空静渡 刚开始学习c的人都会遇到这样的问题 定义一个类 class A这个类里面使用了类B的对象b然后定义了一个类B里面也包含了一个类A的对象a就成了这样 //a.h #include b.h class A { .... private: B b; }; //b.h #include a.h class B { .... private: A a; }; 一编译就出现了一个互包含的问题了这时就有人跳出来说这个问题的解决办法可以这样在a.h文件中声明类B然后使用B的指针。 //a.h //#include b.h class B; class A { .... private: B b; }; //b.h #include a.h class B { .... private: A a; }; 然后问题就解决了。 但是有人知道问题是为什么就被解决的吗也就是说加了个前置声明为什么就解决了这样的问题。下面让我来探讨一下这个前置声明。 类的前置声明是有许多的好处的。 我们使用前置声明的一个好处是从上面看到当我们在类A使用类B的前置声明时我们修改类B时只需要重新编译类B而不需要重新编译a.h的当然在真正使用类B时必须包含b.h。 另外一个好处是减小类A的大小上面的代码没有体现那么我们来看下 //a.h class B; class A { .... private: B *b; .... }; //b.h class B { .... private: int a; int b; int c; }; 我们看上面的代码类B的大小是12在32位机子上。 如果我们在类A中包含的是B的对象那么类A的大小就是12假设没有其它成员变量和虚函数。如果包含的是类B的指针*b变量那么类A的大小就是4所以这样是可以减少类A的大小的特别是对于在STL的容器里包含的是类的对象而不是指针的时候这个就特别有用了。 在前置声明时我们只能使用的就是类的指针和引用因为引用也是居于指针的实现的。 那么我问你一个问题为什么我们前置声明时只能使用类型的指针和引用呢 如果你回答到那是因为指针是固定大小并且可以表示任意的类型那么可以给你80分了。为什么只有80分因为还没有完全回答到。 想要更详细的答案我们看下下面这个类 class A { public: A(int a):_a(a),_b(_a){} // _b is new add int get_a() const {return _a;} int get_b() const {return _b;} // new add private: int _b; // new add int _a; }; 我们看下上面定义的这个类A其中_b变量和get_b()函数是新增加进这个类的。 那么我问你在增加进_b变量和get_b()成员函数后这个类发生了什么改变思考一下再回答。 好了我们来列举这些改变 第一个改变当然是增加了_b变量和get_b()成员函数 第二个改变是这个类的大小改变了原来是4现在是8。 第三个改变是成员_a的偏移地址改变了原来相对于类的偏移是0现在是4了。 上面的改变都是我们显式的、看得到的改变。还有一个隐藏的改变想想是什么。。。 这个隐藏的改变是类A的默认构造函数和默认拷贝构造函数发生了改变。 由上面的改变可以看到任何调用类A的成员变量或成员函数的行为都需要改变因此我们的a.h需要重新编译。 如果我们的b.h是这样的 //b.h #include a.h class B { ... private: A a; }; 那么我们的b.h也需要重新编译。 如果是这样的 //b.h class A; class B { ... private: A *a; }; 那么我们的b.h就不需要重新编译。 像我们这样前置声明类A class A; 是一种不完整的声明只要类B中没有执行需要了解类A的大小或者成员的操作则这样的不完整声明允许声明指向A的指针和引用。 而在前一个代码中的语句 A a; 是需要了解A的大小的不然是不可能知道如果给类B分配内存大小的因此不完整的前置声明就不行必须要包含a.h来获得类A的大小同时也要重新编译类B。 再回到前面的问题使用前置声明只允许的声明是指针或引用的一个原因是只要这个声明没有执行需要了解类A的大小或者成员的操作就可以了所以声明成指针或引用是没有执行需要了解类A的大小或者成员的操作的。 转自http://blog.csdn.net/rogeryi/archive/2006/12/12/1439597.aspx 这篇文章很大程度是受到Exceptional C (Hurb99)书中第四章 Compiler Firewalls and the Pimpl Idiom (编译器防火墙和Pimpl惯用法) 的启发这一章讲述了减少编译时依赖的意义和一些惯用法其实最为常用又无任何副作用的是使用前置声明来取代包括头文件。 Item 26 的Guideline - Never #include a header when a forward declaration will suffice 在这里我自己总结了可以使用前置声明来取代包括头文件的各种情况和给出一些示例代码。 首先我们为什么要包括头文件问题的回答很简单通常是我们需要获得某个类型的定义(definition)。那么接下来的问题就是在什么情况下我们才需要类型的定义在什么情况下我们只需要声明就足够了问题的回答是当我们需要知道这个类型的大小或者需要知道它的函数签名的时候我们就需要获得它的定义。 假设我们有类型A和类型C在哪些情况下在A需要C的定义
A继承至CA有一个类型为C的成员变量A有一个类型为C的指针的成员变量A有一个类型为C的引用的成员变量A有一个类型为std::listC的成员变量A有一个函数它的签名中参数和返回值都是类型CA有一个函数它的签名中参数和返回值都是类型C它调用了C的某个函数代码在头文件中A有一个函数它的签名中参数和返回值都是类型C(包括类型C本身C的引用类型和C的指针类型)并且它会调用另外一个使用C的函数代码直接写在A的头文件中C和A在同一个名字空间里面C和A在不同的名字空间里面 1没有任何办法必须要获得C的定义因为我们必须要知道C的成员变量成员函数。 2需要C的定义因为我们要知道C的大小来确定A的大小但是可以使用Pimpl惯用法来改善这一点详情请 看Hurb的Exceptional C。 34不需要前置声明就可以了其实3和4是一样的引用在物理上也是一个指针它的大小根据平台不同可能是32位也可能是64位反正我们不需要知道C的定义就可以确定这个成员变量的大小。 5不需要有可能老式的编译器需要。标准库里面的容器像list vectormap 在包括一个listCvectorCmapC, C类型的成员变量的时候都不需要C的定义。因为它们内部其实也是使用C的指针作为成员变量它们的大小一开始就是固定的了不会根据模版参数的不同而改变。 6不需要只要我们没有使用到C。 7需要我们需要知道调用函数的签名。 88的情况比较复杂直接看代码会比较清楚一些。 C doToC(C); C doToC2(C c) {return doToC(c);};从上面的代码来看A的一个成员函数doToC2调用了另外一个成员函数doToC但是无论是doToC2还是doToC它们的的参数和返回类型其实都是C的引用(换成指针情况也一样)引用的赋值跟指针的赋值都是一样无非就是整形的赋值所以这里即不需要知道C的大小也没有调用C的任何函数实际上这里并不需要C的定义。 但是我们随便把其中一个C换成C比如像下面的几种示例 1. C doToC(C); C doToC2(C c) {return doToC(c);}; 2. C doToC(C); C doToC2(C c) {return doToC(c);}; 3. C doToC(C); C doToC2(C c) {return doToC(c);}; 4. C doToC(C); C doToC2(C c) {return doToC(c);};无论哪一种其实都隐式包含了一个拷贝构造函数的调用比如1中参数c由拷贝构造函数生成3中doToC的返回值是一个由拷贝构造函数生成的匿名对象。因为我们调用了C的拷贝构造函数所以以上无论那种情形都需要知道C的定义。 9和10都一样我们都不需要知道C的定义只是10的情况下前置声明的语法会稍微复杂一些。 最后给出一个完整的例子我们可以看到在两个不同名字空间的类型A和CA是如何使用前置声明来取代直接包括C的头文件的 A.h Cpp代码 #pragma once #include list #include vector #include map #include utility //不同名字空间的前置声明方式 namespace test1 { class C; } namespace test2 { //用using避免使用完全限定名 using test1::C; class A { public: C useC(C); C doToC(C); C doToC2(C c) {return doToC(c);}; private: std::listC _list; std::vectorC _vector; std::mapC, C _map; C* _pc; C _rc; }; }