Bootstrap

C++中的各种循环依赖和解决办法

智能指针的循环引用问题

问题描述

智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄露。下例是一个典型的循环引用的场景。

struct A
{
	std::shared_ptr<B> bptr;
	~A()	{ cout << "A is deleted ! " <<endl; }
};

struct B
{
	std::shared_ptr<A> aptr;
	~B()	{ cout << "B is deleted! " << endl; }
};

void TestPtr()
{
	std::shared_ptr<A> ap(new A);
	std::shared_ptr<B> bp(new B);
	ap->bptr = bp;
	bp->aptr = ap;
}

测试结果是两个指针A和B都不会被删除,存在内存泄露。循环引用导致ap和 bp 的引用计数为2,在离开作用域之后,ap和 bp 的引用计数减为1,并不会减为0,导致两个指针都不会被析构,产生了内存泄露。

1、weak_ptr解决循环引用问题

上述循环引用问题通过weak_ptr就可以解决,只要将A或B的任意一个成员变量改为weak_ptr即可。

struct A
{
	std:: shared_ptr<B> bptr;
	~A(){ cout << "A is deleted! " << endl; }
} ;

struct B
{
	std::weak_ptr<A> aptr; 	//改为weak_ptr
	~B() { cout <<"B is deleted!" <<endl; }
};

void TestPtr()
{
	{
		std::shared_ptr<A> ap (new A);
		std::shared_ptr<B> bp (new B);
		ap->bptr = bp;
		bp->aptr = ap;
	}	//objects should be destroyed .
}

在对B的成员赋值时,即执行bp->aptr=ap;时,由于aptr是 weak_ptr,它并不会增加引用计数,所以ap 的引用计数仍然会是1,在离开作用域之后,ap 的引用计数会减为0A指针会被析构,析构后其内部的 bptr的引用计数会减为1,然后在离开作用域后bp引用计数又从1减为0,B对象也将被析构,不会发生内存泄露。

类的循环依赖问题

问题描述

class B;
class A {
public:
	B b;
};

class B {
public:
	A a;
};


若两个类之间存在循环依赖则在编译时会报错,原因是两个类中存在相互的调用,无法为两个类分配具体的空间。
即使已经进行了前向声明,但是A、B类完全依赖彼此,各自编译都需要彼此的完整定义,此时编译器是无法办到的。

1、使用前向声明

A.h
class B;
class A {
public:
	B *b;
	int fun(B* b);
};

B.h
class B {
public:
	A *a;
	int fun(A*a);
};


由于前向声明而没有定义的类是不完整的,所以class B只能用于定义指针、引用、或者用于函数形参的指针和引用,不能用来定义对象,或访问类的成员。
这是因为需要确定class A空间占用的大小,而类型B还没有定义不能确定大小,但B是指针类型大小已知,因此Class A中可以使用B的指针定义成员变量。

2、抽象出父类,A,B作为派生类

用指针替代变量声明的方法虽然能够编译通过,但是存在一个问题,就是无法进行单元测试。测A需要B是完整的,B又依赖A,那A到底要怎么测试。为了能够进行单元测试,可以考虑将方法和数据抽出作为父类。

class Base {   
 public:	
     int fun1();
     int fun2();
 };
 
class A {
public:
int funA() { return fun1(); };

class B {
public:
int funB() { return fun2(); };
};

在这里插入图片描述

3、借助第三类抽离耦合部分函数

// *.h
struct Helper {
    static int doFunA(); 
    static int doFunB();
};
class A {
public:
	int funA();
	int data_;
};
class B {
public:
	int funB();
};

// *.cpp
int Helper::doFunA() {
	A a;
	return a.data_;
}
int Helper::doFunB(){ 
	B b;
	return b.funB();
}
int A::funA() {
	return Helper::doFunB();
}
int B::funB(){ 
	return Helper::doFunA();
}

在这里插入图片描述

4、抽象基类,分离接口和实现

#include <iostream>
using namespace std;

class IB
{
public:
    virtual int getVal() = 0;
};

class B : public IB
{
public:
    int val = 3;
    int getVal() override {
        return val;
    };
    int getValofA();
};

class A
{
public:
    IB* b;
    int val = 2;
    int getValOfB()
    {
        return b->getVal();
    }

};

int B::getValofA() {
    A a;
    return a.val;
}

int main()
{
    IB* b = new B();
    A a;
    B bb;
    a.b = b;

    cout << a.getValOfB() << endl;
    cout << bb.getValofA() << endl;
    return 0;
}

在这里插入图片描述

5、函数绑定

以上方法其实类之间还是存在依赖关系,函数的调度对于调用者来说,其实只关心需要传入的参数、能够返回的结果,至于是哪个对象,内部如何实现的,调用者是无感的。因此可以利用函数绑定来解除依赖关系。

#include <iostream>
#include <functional>

using namespace std;

class B { // 下层类型,可以不知道A类型的存在
public:
    void method1(int n, function<void(int)> func) { func(n); } 
    int method2(int n1, int n2, function<int(int, int)> func) { return func(n1, n2); } 
    void method3(const string& name, function<void(const string&)> func) { func(name); };
    int method4(const int& n1, const int& n2, function<int(const int&, const int&)> func) { return func(n1, n2); };
    int method5(const int& n1, const int& n2, function<int(const int&, const int&)> func) { return func(n1, n2); };
};

class A { // 上层类型
public:
    void set_value(int n) { i_a = n; }
    int get_value() { return i_a; }
    static int add(int a, int b) { return a + b; }
private:
    int i_a;
};

void show(const string& name) {
    cout << name << endl;
}

struct funopt {
    int operator()(const int& a, const int& b){
        return a * b;
    }
};

int main() {
    A a;
    B b;
    a.set_value(1);
    cout << a.get_value() << endl; // 输出1

    //绑定类成员函数
    auto bindfun1 = bind(&A::set_value, &a, placeholders::_1);      // 绑定显式对象成员函数的指针
    b.method1(10, bindfun1);
    cout << a.get_value() << endl; // 输出10

    //绑定静态函数
    auto bindfun2 = bind(&A::add, placeholders::_1, placeholders::_2);
    int ret = b.method2(10, 15, bindfun2);
    cout << ret << endl; // 输出25

    //绑定普通函数
    auto bindfun3 = bind(show, placeholders::_1);
    b.method3("JIE", bindfun3); //输出JIE

    //绑定lambda表达式
    auto bindfun4 = bind([](const int& a, const int& b) {return a > b ? a : b; }, placeholders::_1, placeholders::_2);
    ret = b.method4(5, 3, bindfun4);
    cout << ret << endl; // 输出5

    //绑定仿函数
    auto bindfun5 = bind(funopt(), placeholders::_1, placeholders::_2);   // 绑定匿名对象成员函数的引用
    ret = b.method4(5, 3, bindfun5);
    cout << ret << endl; // 输出15

    return 0;
}

动态库循环依赖

问题描述

假设存在两个不相互依赖动态库A和动态库B,如何实现跨DLL调用函数。

接口和回调函数

动态库B(接口定义)
假设动态库B提供了一个接口,用于处理某种事件。这个接口包含一个纯虚函数handleEvent,用于处理事件。

// 动态库B的头文件(example.h)
class IEventHandler {
public:
    virtual &#126;IEventHandler() {}
    virtual void handleEvent() = 0; // 纯虚函数
};

动态库A(实现回调)
在动态库A中,我们实现了一个类MyEventHandler,该类实现了IEventHandler接口,并提供了事件处理的具体实现。

// 动态库A的头文件(myeventhandler.h)
#include "example.h"
class MyEventHandler : public IEventHandler {
public:
    void handleEvent() override {
        // 处理事件的代码逻辑
    }
};

动态库B(使用回调)
在动态库B中,我们可以通过传递回调函数的指针或引用,将MyEventHandler实例传递给需要处理事件的函数或对象。

// 动态库B的代码(example.cpp)
#include "example.h"
#include "myeventhandler.h" // 引入动态库A的头文件

void someFunction(IEventHandler* eventHandler) {
    // 使用传递进来的事件处理器处理事件
    eventHandler->handleEvent(); // 调用回调函数处理事件
}

主程序(加载和使用)
在主程序中,我们需要加载动态库B和动态库A,并创建MyEventHandler的实例,将其传递给动态库B中的函数。

#include <iostream>
#include "example.h" // 引入动态库B的头文件
#include "myeventhandler.h" // 引入动态库A的头文件

int main() {
    // 加载动态库B和A(这里仅为示意,具体加载方式取决于你的环境)
    // ...
    MyEventHandler myEventHandler; // 创建事件处理器实例
    someFunction(&myEventHandler); // 将事件处理器传递给动态库B中的函数
    return 0;
}

在这个例子中,通过使用纯虚函数和接口,动态库B定义了一个通用的回调接口,而动态库A提供了该接口的具体实现。主程序通过加载两个动态库,并将实现类的实例传递给接口,实现了动态库之间的回调机制。这种方式使得动态库B和A之间的耦合度降低,提高了代码的可扩展性和可维护性。

动态库A通过实现动态库B中定义的接口类(如IEventHandler),提供了事件处理的具体实现。这意味着动态库A提供了对动态库B接口的具体实现,并依赖于动态库B的接口来与动态库B进行交互。通过使用接口和回调机制,动态库A可以独立于动态库B进行开发和测试,而不需要直接依赖动态库B。同时,动态库B也可以独立于动态库A进行开发和测试,

参考文献

http://t.csdnimg.cn/iWYPw
http://t.csdnimg.cn/2P0oP
http://t.csdnimg.cn/GaQLp

;