差不多一年时间没用过C++写过程序了,由于工作的需要,我又回到了C++的阵形。在工作的过程中遇到了很多麻烦,当我往工程里加一个类,而且那个类又与工程里的类相关,如有那个类型的成员变量。情况如下
//A.h///
class A
{
.......
};
B.h//
class B:A
{
....
A member;
}
结果,编译就会出错,说找不到类形A。解决的办法是在B.h里#include “A.h”。但是有时候不用#include “A.h”,只要在classB:A前加class A;就可以了。更严重的是不但要#include “A.h”,还要class A;。
起初觉得没问题,因为这样搞来搞去总会编译通过的,而且不会让程序变大,因为有#ifndef...#endif和#pragma once控制。直到有一次,我需要那些常量放到一个文件中“const.h”,然后include到其它需要它的类中,结果怎么也编译不成功(因为文件多了,而且每个文件都这样互相include,把我也蒙了)
直到今天终于从《Effective C++》里找到原理。现向大家分享一下,首先我以下面这个类结构作例子。(先不管我为什么不加一个Woman,为什么Man就有child,我只是作例子解说,绝没有性别歧视。
代码如下:
main.h//
#include "stdafx.h"
#include "man.h"
int main(){
    Man m;
    return 0;
}
Person.h/
#pragma once
class Person
{
public :
    Person(void);
    ~Person(void);
};
Person.cpp///
#include "StdAfx.h"
#include "./person.h"
Person::Person(void){
}
Person::~Person(void){
}
/Man.h///
#pragma once
#include "person.h"
class Man : public Person
{
public :
    Man(void);
    ~Man(void);
private :
    Person child;
};
/Man.cpp//
#include "StdAfx.h"
#include "./man.h"
Man::Man(void){
}
Man::~Man(void){
}
上述代码,编译运行一切正常。现在我作以下修改:
/Man.h///
#pragma once
//#include "person.h"        // 去掉
class Man : public Person
{
public :
    Man(void);
    ~Man(void);
private :
    Person child;
};
/Man.h///
#pragma once
//#include "person.h"   // 去掉
class Person;           // 加入
class Man:public Person
{
public :
    Man(void);
    ~Man(void);
private :
    Person child;
};
error C2504: “Person” : 未定义基类
error C2504: “Person” : 未定义基类
/Man.h///
#pragma once
//#include "person.h"   // 去掉
class Person;           // 加入
class Man:public Person
{
public :
    Man(void);
    ~Man(void);
private :
    Person *child;      // 改为指针
};
/Man.h///
#pragma once
//#include "person.h"   // 去掉
class Person;           // 加入
class Man               // 去掉:Person
{
public :
    Man(void);
    ~Man(void);
private :
    Person *child;      // 改为指针
};
error C2504: “Person” : 未定义基类
编译通过
要讲解上面的代码还要一些预备知备,看下面代码:
int main()
{
    int x;
    Person p;// 用C++时编译不通过;
}
当编译器看到 x定义式时,它们知道必须配置足够的空间以放置一个int。没问题,每个编译器都知道int有多大。然而当编译器看到p的定义式时,虽然它们也知道必须配置足够空间以放置一个Person,但一个Person对象有多大呢?编译器获得这项信息的唯一办法就是询问class定义式。然而class的定义式可以合法地不列出实现细节(如:
只写出 class Person;)那么编译器又如何知道该配置多少空间呢?
对 Java等语言对此问题的解法是,当程序定义出一个对象时,只配置足够空间给一个“指向该对象的指针”使用,如
public Person;
public static void main(String[] args)
{
    Person p;
}
对于 C++就如下那样:
class Person;
int main()
{
    Person *p;// 编译器当要配置一个指针大小的空间的指针给p就可以了。
    //Person &p2; 这个理论上也可以,但references object必须“言之有物”
    return 0;
}
看回刚才那段代码为什么“ Person p;//用C++时编译不通过;”呢?因为它要调用Person constructor。那就是Person的实现细节。
现在可以解说上面的表格了,我的目的是 去掉#include Person.h并加入class Person; 所以要做有:
1.     将 Person child改为Person *child。因为child也是Man的成员,Man的大小与Child相关,而child不是内部类型,它的大小编译器不知道。
2.     将 :public Person去掉。因为Man继承Person,所以编译器也要知道Person是怎样实现的,那样才能构造出正确的Man来(为了编译成功,我忍痛割爱了)。
同时我也要对原码作一下解释:
/Man.h///
#pragma once
#include "person.h"
class Man : public Person
{
public :
    Man(void);
    ~Man(void);
private :
    Person child;
};
这里 #include “person.h”不但包含了 Person的定义,也包含了Person的实现细节,所以是编译成功的。
结论
1.     当不需要调用类的实现时,包括 constructor,copy constructor,assignment operator,member function,甚至是address-of operator时,就不用#include,只要forward declaration就可以了。
2.       当要用到类的上面那些“方法”时,就要 #include
扩充
为了加深认识,我分享遇到的另一情况。
Person.h/
#pragma once
class Person
{
public :
    Person(void);
    ~Person(void);
    virtual void addChild(Person p) = 0;// 将Person变为抽象类
};
/Man.h///
#pragma once
//#include "person.h"   // 去掉
class Person;           // 加入
class Man               // 去掉:Person
{
public :
    Man(void);
    ~Man(void);
private :
    Person *child;
    void addChild(Person p);// 相应地在Man.cpp中加上这个空函数
};
error C2259: “Person” : 不能实例化抽象类
/Man.h///
#pragma once
#include "person.h"    // 加回来
class Person;      // 加不加入也没所谓
class Man               // 去掉:Person
{
public :
    Man(void);
    ~Man(void);
private :
    Person *child;
    void addChild(Person p);// 相应地在Man.cpp中加上这个空函数
};
/Man.h///
#pragma once
#include "person.h"    // 加回来
class Person;      // 加不加入也没所谓
class Man               // 去掉:Person
{
public :
    Man(void);
    ~Man(void);
private :
    Person *child;
    void addChild(Person *p);// 将形参变为Person*
};
error C2259: “Person” : 不能实例化抽象类
编译成功
/Man.h///
#pragma once
#include "person.h"    // 加回来
class Person;      // 加不加入也没所谓
class Man               // 去掉:Person
{
public :
    Man(void);
    ~Man(void);
private :
    Person *child;
    void addChild(Person &p);// 将形参变为Person&
};
编译成功
为什么出现不能实例化抽象类?我并没有实例化过它。
这是参数的传递问题。当一个变量传给函数时,我们说是实参传给形参(pass-by-value),形参是通过copy constructor建立的,所以就是实例化了一个抽象类。而pass-by-reference和传指针就没问题了。(全文完)
参考资料:
候捷:《Effective C++》

正确使用#include和前置声明(forward declaration)相关推荐

  1. Linux内核编程广泛使用的前向声明(Forward Declaration)

    前向声明 编程定律 先强调一点: 在一切可能的场景,尽可能地使用前向声明(Forward Declaration).这符合信息隐蔽的原则. 一个例子 regmap 那么前向声明究竟是个什么鬼? 在内核 ...

  2. linux 没有windows.h头文件_宋宝华: Linux内核编程广泛使用的前向声明(Forward Declaration)...

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者:宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前向声明 编程定律 先强调一点:在一切可 ...

  3. C++基础(1)- 声明(前向声明 Forward Declaration)与定义

    C++基础(1)- 声明(前向声明 Forward Declaration)与定义 如需转载请标明出处:http://blog.csdn.net/itas109 技术交流:129518033 文章目录 ...

  4. C++中前置声明介绍

    前置声明是指对类.函数.模板或者结构体进行声明,仅仅是声明,不包含相关具体的定义.在很多场合我们可以用前置声明来代替#include语句. 类的前置声明只是告诉编译器这是一个类型,但无法告知类型的大小 ...

  5. c++ using 前置声明_C++ 类声明 类前置声明范例

    在编写C++程序的时候,偶尔需要用到前置声明(Forward declaration).下面的程序中,带注释的那行就是类B的前置说明.这是必须的,因为类A中用到了类B,而类B的声明出现在类A的后面.如 ...

  6. C语言的结构体前置声明,?C语言的不完整类型和前置声明

    声明与定义(Declaration and Definition) 开始这篇文章之前,我们先弄懂变量的declaration和definition的区别,即变量的声明和定义的区别. 一般情况下,我们这 ...

  7. forward declaration of class 错误

    在使用Qt的时候遇到这个错误,查了一下发现,是因为我没有正确的使用前置声明. 1 #ifndef FIRSTPAGE_H 2 #define FIRSTPAGE_H 3 4 #include &quo ...

  8. error: invalid use of incomplete type 'XXXX' ;error: forward declaration of 'XXXX' 声明改为包含头文件

    error: invalid use of incomplete type 'XXXX' error: forward declaration of XXXX 声明改为包含头文件 class XXXX ...

  9. 使用类前置声明的好处-结合Qt 4一个主窗口实例讲解

    本实例实现一个基本的主窗口程序,包含一个菜单条.一个工具栏.中央可编辑窗体及状态栏. 主窗口头文件代码如下: 1 #ifndef MAINWINDOW_H 2 #define MAINWINDOW_H ...

最新文章

  1. Log4Net的控制台,WinForm,WebApplication使用
  2. 万物皆可JOJO:这个GAN直接让马斯克不做人啦 !Demo在线可玩!
  3. go打造以太坊合约测试框架
  4. boost asio 简单示例
  5. Atom 插件备份--Sync Setting
  6. 如何在 ASP.NET CORE 中获取客户端 IP ?
  7. [html] 对于rtl网站的适配有哪些方案?
  8. py 字典添加多个value_# Python 3 # Python 3字典Dictionary(1)
  9. 通过导入txt数据画出python turtle图形_【Python】txt文件读取绘画
  10. 优秀REST风格 API的设计原则
  11. [原创]如何从数据库层面检测两表内容的一致性
  12. Prescan(一):无人驾驶仿真软件简介
  13. 如何做肌电信号手势识别?
  14. 彻底卸载McAfee Agent
  15. Ubuntu虚拟机下载(清华大学开源软件镜像源)
  16. 国内外免费公用mqtt测试服务器推荐
  17. 基于GoogleMap,Mapabc,51ditu,VirtualEarth,YahooMap Api接口的Jquery插件的通用实现(含源代码下载) --转...
  18. Linux下彻底删除oracle
  19. EZSP-UART 入门
  20. Confidence Propagation Cluster: 一个来自CVPR2022的目标检测涨点神器(CP-Cluster)

热门文章

  1. 阿里系App抓包分析(三)
  2. 利用Google Map显示指定位置地理位置
  3. Xrm.Page.data.entity Properties and Methods
  4. CentOS7.4配置OpenLDAP Client集成AD服务及SSSD服务与SSH服务
  5. python读json文件json.decoder.JSONDecodeError终极解决大法
  6. 启明云端分享|盘点ESP8684开发板有哪些功能
  7. 算法学习——数字旋转方阵
  8. 新型变色纹身自带传感器,联动手机APP可同时监测血糖、pH值、白蛋白含量
  9. 渭师院的计算机专业学什么课程,【三名+建设工作】渭南初级中学教师郭晓辉走进渭师院给大学生上课...
  10. Linux下 IPMItool配置方法(MSI主板)