cpp前向声明

当我们使用cpp设计数据结构时,常常出现两个类互相依赖的情况,如下,假设我们有一个test.h文件,里面包含两个类的定义:

// A的成员变量包含B对象,B的成员变量包含A对象
class A {
    B b_;
};

class B {
    A a_;
};

前向声明

首先,我们会碰到一个问题,在class A定义时,class B还没有被定义,那么编译器会报错找不到class B,那么这时候前向声明就派上用场了,我们可以在class A定义之前给出class B的前向声明,像下面这样,这样编译器就能明白存在class B这样一个类:

// class B的前向声明
class B;

class A {
    B b_;
};

class B {
    A a_;
};

但是这样我们的代码还是不能编译通过。因为前向声明仅仅给了编译器一个提示,存在class B这样一个类,但是并没有这个类的完整定义。那么当编译器想要定义A类的对象时,发现需要B类的完整对象定义,再去找B类的完整定义时,发现又需要A类的完整定义,出现相互依赖的情况。而且在互相依赖的情况,因为两个类都包含了对方的完整对象,这种递归让编译器无法计算出两个类的大小,因此需要将对象类对象声明为一个指针,指针的大小是固定的,因此编译器能够顺利计算出两个类的大小,并顺利通过编译,类似下面这样

class B;

class A {
    B *b_;
    // 智能指针也是可以的,std::unique_ptr<B> b_;
}

class B {
    A *a_;
}

继续深入

由于类在定义时支持定义不完整类的指针(例如:前向声明),这样我们的代码就能通过编译了。

但是当我们继续深入,想要在A类中调用B类的成员变量或者方法时,就又会出现问题,类似下面这种情况:

class B;

class A {
    B *b_;
    // 智能指针也是可以的,std::unique_ptr<B> b_;
    void write_A() {
        b_->write_B();
    }
}

class B {
    A *a_;

    void write_B() {
        // implement
    }
}

这种情况是因为从class A的视角来看,现在它只拥有class B的前向声明,而前向声明只是一种类的不完整定义,claas A并不知道class B包含哪些成员变量或者方法,因此这种情况下编译器会报错:不能调用不完整类型B的方法。遇到这种情况就只能让class A事先知道有write_B()这个方法的存在,最好的解决办法就是,再额外创建一个.cpp文件包含实现,并包含class Aclass B的头文件定义,而在头文件中则只保留前向声明,类似下面这样:

// test.h文件
class A;
class B;

class A {
    B *b_;
    void write_A();
}
class B {
    A *a_;
    void write_B();
}
// test.cpp文件
#include "test.h"

A:write_A() {
    b_->write_B();
}

B:write_B() {
    // implement
}

总结

这样我们就完美的解决了两个类之间互相依赖的问题,不过这种方式必须引入一个cpp文件的存在,按我的习惯,我很喜欢把实现都放在.h文件里,因为这样我只需要包含这个头文件就能直接用这个类,而不用链接,比较方便。不过这种方式只适合简单的类定义,复杂的类还是要规范地将定义和实现分开。