• (转)正确使用#include和前置声明(forward declaration)


    差不多一年时间没用过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){
    }
    Code
    /////////Man.h///////////
    #pragma once
    //#include "person.h"        //去掉
    class Man : public Person
    {
    public:
        Man(
    void);
        
    ~Man(void);
    private:
        Person child;
    };
    Code
    /////////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” : 未定义基类
    Code
    /////////Man.h///////////
    #pragma once
    //#include "person.h"   //去掉
    class Person;           //加入
    class Man:public Person
    {
    public:
        Man(
    void);
        
    ~Man(void);
    private:
        Person 
    *child;      //改为指针
    };
    Code
    /////////Man.h///////////
    #pragma once
    //#include "person.h"   //去掉
    class Person;           //加入
    class Man               //去掉:Person
    {
    public:
        Man(
    void);
        
    ~Man(void);
    private:
        Person 
    *child;      //改为指针
    };
    error C2504: “Person” : 未定义基类
    编译通过
    Code
    要讲解上面的代码还要一些预备知备,看下面代码:
    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
     
    扩充
        为了加深认识,我分享遇到的另一情况。
    Code
    ////////////Person.h/////////////
    #pragma once
    class Person
    {
    public:
        Person(
    void);
        
    ~Person(void);
        
    virtual void addChild(Person p) = 0;//将Person变为抽象类
    };
    Code
    /////////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” : 不能实例化抽象类
    Code
    /////////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中加上这个空函数
    };
    Code
    /////////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” : 不能实例化抽象类
    编译成功
    Code
    /////////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++》
  • 相关阅读:
    互联网公司做智能硬件要注意什么?
    移动GPU全解读(二)
    java JDK设置环境变量
    hive load文件第一个字段为NULL
    网页版微信和微信公共号扫码登陆原理分析
    Java&Xml教程(九)Java中通过XSD校验XML合法性
    47.Android 自己定义PopupWindow技巧
    ArcGIS中数据之间的转换接口IFeatureDataConverter2
    怎样衡量代码质量?
    iOS -读书笔记-网络请求
  • 原文地址:https://www.cnblogs.com/fjchenqian/p/1492019.html
Copyright © 2020-2023  润新知