C++ 头文件的循环引用

前言

在实现设计模式中的观察者模式时,想要完成的功能是:被观察者数据改变时通知观察者,而观察者能够知悉是哪些数据发生了改变(被观察者作为参数传递)

这里就产生了循环引用,被观察者中包含了观察者,而观察者也包含了被观察者,导致出错。

解决方案:前向声明

在形如以下的循环引用结构,可以通过前向声明解决

// A.h
#include "B.h"
class B;

class A{
public:
    B* member1;
    B& member2;
    void func(B*);
    void func(B&);
};

// A.cpp
...

// B.h
#include "A.h"
class A;

class B{
public:
	A* member1;
    A& member2;
	void func(A&);
	void func(A*);
};

// B.cpp
...

要注意,类 A 包含类 B 或类 B 包含类 A 只能以指针、引用、或者用于函数形参的指针和引用的形式,且其实现必须在源文件中实现,而无法在头文件中。

这是因为编译器构造类时必须知道所占用的内存大小,因为存在循环引用,在构造一个类时,无法知道另一个类的大小,所以此时只能用指针或引用(此时内存大小是可以确定的)

此时如果不采用前向声明,则会报错:

'x' has not been declared
'x' does not name a type

解决方案:解除循环引用

在上述的循环引用结构中,AB 互相引用,是一种高度耦合的状态,此时出现一个第三者,则可以打破这种循环结构,也对二者进行解耦。

AB 的相互引用,无非是方便获取数据或调用某些操作,可以通过第三者接口来进行循环的破坏。

07-1

通过接口 C 解耦 AB,现在 B 无需知道 A 的具体细节,只需要调用 C 提供的接口即可。

07-2

此时代码:数据以 int 为例

// A.h
#include "C.h"

class A{
public:
    void func(){
        std::cout << member->getData() << std::endl;
    }
    C* member;
};

// B.h
#include "C.h"
#include "A.h"

class B : public C{
public:
    int getData() override {
        return data;
    }
    void func(A& a){
        a.func();
    }
    int data;
};

// C.h

class C{
public:
    virtual int getData() = 0;
};

解决方案:将数据作为参数

如上所述,循环引用无非是获取某些数据或调用某些函数,此时可以将一方的引用去除,而转为接受参数。但是这种方案割舍了某些功能(只能单向通知),但是作为观察者模式的实现,可以比较好的完成。

被观察者数据改变时通知观察者,而观察者能够知悉是哪些数据发生了改变,这些数据通过参数传递。

简化后的结构:

// Subject.h
class Subject{
public:
    void setData(int data){
        data_ = data;
        notifyObservers();
    }
    ...
private:
	void notifyObservers(){
        obs_->update(data_);
    }
    Observer* obs_;
    int data_;
};

// Observer.h
class Observer{
public:
    void update(int data){
        std::cout << "监听到数据改变: " << data << std::endl;
    }
};
上一篇 下一篇

评论 | 0条评论