您现在的位置是:首页 >技术杂谈 >C++的类声明、前置声明、定义及各自优势、使用场景网站首页技术杂谈

C++的类声明、前置声明、定义及各自优势、使用场景

Gov_Demon 2024-07-04 06:01:03
简介C++的类声明、前置声明、定义及各自优势、使用场景

当一个class的成员变量为另外一个类的指针类型的时候,此时,我们可以不在头文件中来包含这个类的头文件,即不使用include“.h”,而是采用前置声明的形式,对这个类进行一个类型的前置声明。
类的前置声明形式上:只有class关键字+类名,后面不带任何符号,即{}
白话解释:哎,b是个类,我要用它,你别管,它在某个地方是个真实的存在(也可能不存在,我就tm忽悠你的), 总之你不要管,我要用它”。这样跟编译器打过招呼之后,就能保证代码编译通过。
如下代码段

//文件a.h
 class B;
 class A
 {
 public:
 B *b;    
 };
 ​
 //文件b.h
 class B
 {
 public:
     void func1();
 };

a.h和b.h这两个文件中定义了两个类分别是A和B。其中A类有一个成员b是Class B的指针,这个时候可使用类前置声明

前置声明省去了#include的处理、降低文件之间的编译依赖,从而避免不必要的编译时间浪费。

利用前置声明的利弊

优点:避免#include, 避免修改一个被包含的头文件,导致整个文件重新编译。

缺点:(摘自Google)

如果前置声明关系到模板,typedefs, 默认参数和 using 声明,就很难决定它的具体样子了。

很难判断什么时候该用前置声明,什么时候该用 #include, 特别是涉及隐式转换运算符的时候。极端情况下,用前置声明代替 includes 甚至都会暗暗地改变代码的含义

前置声明函数或模板有时会害得头文件开发者难以轻易变动其 API. 就像扩大形参类型,加个自带默认参数的模板形参等等。

前置声明来自命名空间 std:: 的 symbol 时,其行为未定义。

仅仅为了能前置声明而重构代码(比如用指针成员代替对象成员),后者会变慢且复杂起来。

还没有实践证实前置声明的优越性。

前置声明作用:
告诉编译器,这个类在其它地方定义,真正使用类的定义的时候才进行编译。
Note:类A在编译的时候不需要拿到类B的定义是因为这里面定义的是指针,而对于指针是不需要定义就可以进行内存布局的,在编译A的类的声明的时候,在进行内存布局的时候是不需要拿到B的定义的

但是在A的成员函数中如果使用这个指针b,则就需要在A对应的.cpp文件中引入B的定义。也就是在.cpp中需要include b.h

这里面对于前置声明,还需要注意一个事情,就是如果另外有一个类,这个类使用了类A,并且通过类A访问使用了A的成员变量b,比如下边这种情况

//文件c.h
 #include "a.h"
 class C
 {
 public:
     void print(){
         a.b->func1();
    }
    A a;
 };

编译时,讲会报错:不允许使用不完全(incomplete)类型A。
Because:C中访问了a成员b的成员函数。所以,此时也需要类B的完整定义,即include

比较正规的做法就是在C的头文件中也引入B的定义,这样编译器就能找到B的定义,从而编译通过
如法1

法1
//文件c.h
 #include "a.h"
 #include "b.h"
 class C
 {
    ...
 }
 法2
 //文件a.h
 #include "b.h"
 //class B;
 class A
 {
 public:
 B *b;    
 };

法2在类A的头文件中引入b.h头文件
这种写法,就是放弃了前置声明,直接在A中引入B的头文件,一次性将B的全部声明引入到A的头文件中。

这样也可以编译通过,但是会带来一个问题。就是以后所有引用A的头文件的其他文件,在编译的时候都需要编译一遍B,而在这些文件中可能压根就没有访问成员变量b。也就是说这些文件其实根本就不需要B的定义。这样会导致编译这些文件的时候,还需要编译一遍不相关的类B。这会导致编译缓慢

所以,这里面正确的做法是采用前一种方法。也就是说,头文件中能尽量少的引入头文件就应该尽量少的引用收文件,能用前置声明的就应该尽量使用前置声明

几种可以使用前置声明的 地方
**

以指针或引用的形式来引用类型

**

class A;
class B
{
    A *pa;//ok
    A &ra;//ok

    A * f(const A * pa);//ok
    A & f(const A & pa);//ok

};

友元

class A;
class B
{
    friend A; //ok
    friend class A;//ok

};

作为返回值或者参数

class A;
class B
{
    void f(A a);//ok

    A g();//ok
};

**

不可以使用前置声明的类型

**

使用完整的类引用

class A;
class B
{
    //A a; //error
    
};

被当作父类

class E;
class F: public E  //error VC6.0--'E': base class undefined  Qt5.6-invalid use of incomplete of 'class C'
{};
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。