前置申明的正确用法

简单的说,前置申明就是告诉编译器这个变量已经在其他地方实现了,你编译器在一定的条件下就可以只管编,别管我到底是怎么实现的。
而这个特定情况,就是编译器不需要知道这个变量的类型的具体实现的情况下。
什么情况下是编译器不需要知道这个变量的类型的具体信息呢?
    1.这个变量是一个指针。
    2.这个变量是一个引用。
指针和引用因为本身都是四个字节的一种特殊类型,他们能指向其他的变量。他们里面放的无非是内存地址,不管是指向的类的还是内置变量。而被解析成不同的对象的原因是编译器根据这个指针的描述决定的。
而前置申明所能做到的也就是这个而已。同c的extern指示符的效果相同。

而当你需要知道这个类的具体实现的情况下,编译器这关你是过不了的。
class a;
class b:public a
{}

或者
class a;
class b
{
    a b;
}

类似这种写法编译器都需要知道这个class a是如何实现的。从而分配其内存,调用其构造函数等等。

总的来说,使用#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 ){
}
 
上述代码,编译运行一切正常。现在我作以下修改:
/////////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和传指针就没问题了

### 前置声明 (Forward Declaration) 的使用方法及场景 #### 什么是前置声明? 在 C++ 中,前置声明是一种机制,允许开发者提前告知编译器某个类型的名称或存在性,而无需立即提供完整的定义。这有助于减少头文件的相互依赖并优化编译时间。 #### 使用方法 前置声明可以通过 `class` 或 `struct` 关键字来完成。例如: ```cpp class MyClass; // 这是一个类的前置声明 ``` 当使用指针或引用时,前置声明通常就足够了,因为编译器只需要知道该类型的存在即可分配内存地址[^2]。 #### 场景分析 以下是几种常见的使用场景: 1. **避免循环依赖** 当两个类互相包含对方作为成员变量时,可能会导致无法正常编译的情况。通过前置声明可以解决这一问题。 ```cpp class B; // 前置声明 class A { B* bPtr; }; class B { A aInstance; }; ``` 2. **提高编译效率** 如果多个源文件都包含了同一个大型头文件,则每次重新编译都会消耗大量资源。利用前置声明仅引入必要的部分能够显著提升性能。 3. **接口分离** 将具体实现隐藏起来只暴露必要信息给使用者也是软件工程中的良好实践之一。比如,在公共 API 文件里只需做简单的类型说明而不涉及内部细节。 4. **动态分配与智能指针** 对于那些仅仅用于创建实例或者管理生命周期的对象来说(如new/delete),也完全可以依靠forward declarations 来简化代码结构。 #### 示例代码展示如何运用 Forward Declarations 解决实际开发过程遇到的问题: 假设有一个项目需要处理两种不同形状 Shape 和 Circle ,它们之间可能存在某种关联关系如下所示: ```cpp // shape.h #ifndef SHAPE_H_ #define SHAPE_H_ #include "circle.h" class Shape { public: virtual void draw() const =0 ; }; #endif /*SHAPE_H_*/ // circle.h #ifndef CIRCLE_H_ #define CIRCLE_H_ #include "shape.h" class Circle :public Shape{ private : double radius_; public: explicit Circle(double r):radius_(r){} void setRadius(const double &value); double getArea()const ; }; #endif/*CIRCLE_H_*/ ``` 上述例子会出现错误提示:“cannot find file 'circle.h'”。这是因为两者互相对方进行了include操作形成了闭环依赖。此时就可以采用前面提到的方法——即用到的地方先进行前向申明再按需加载真正的定义体从而打破僵局。 最终修改后的版本应该是这样的样子: ```cpp // shape.h 不变... // circle.h 修改为下面形式 #ifndef CIRCLE_H_ #define CIRCLE_H_ class Shape;// 只做一个简单声明就够了! class Circle :public Shape{/*...*/}; #endif/*CIRCLE_H_*/ ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值