分类 编程及辅助 下的文章

C++ | 类的双向耦合:理解与避免


e3987409a24d418baf2be613f38998ad.jpg

背景

在C++编程中,类与类之间的关系常常会产生复杂的依赖,特别是当两个类相互引用时,这种依赖关系被称为双向耦合(Bidirectional Coupling)。这种关系在某些场景下是不可避免的,但也可能导致维护困难、代码复杂度增加、模块化降低等问题。

什么是双向耦合?

双向耦合是指两个类之间相互依赖,A类依赖于B类,而B类也依赖于A类。这种双向的依赖关系在设计模式中常常出现,例如在某些MVC架构中,视图和控制器可能会相互引用。一个简单的例子如下:

class B; // 前向声明
class A
{
    B *b;
    // A 依赖于 B
public:
    void setB(B *b) { this->b = b; }
};
class B
{
    A *a; // B 依赖于 A
public:
    void setA(A *a) { this->a = a; }
};

在这个例子中,类A和类B相互依赖,形成了双向耦合。尽管这个例子简单,但它揭示了在大型项目中,这种耦合可能带来的复杂性。

双向耦合的潜在问题

  1. 增加代码复杂性:双向耦合使得类之间的关系复杂化,导致代码难以理解和维护。
  2. 测试困难:单元测试某个类时,如果它依赖于其他类,那么就必须对这些依赖进行模拟或测试,增加了测试难度。
  3. 降低模块化:当类之间存在双向耦合时,系统的模块化程度下降,类之间的强依赖关系使得代码难以重用。
  4. 维护困难:任何一个类的修改都有可能导致另一个类的修改,从而影响整个系统的稳定性。

双向耦合的常见场景

在实际开发中,双向耦合常常出现在以下几种场景中:

  • 父子关系:一个父类和子类之间的复杂依赖关系,特别是在父类需要访问子类特定功能时。
  • 观察者模式:观察者和被观察者之间可能存在双向耦合,因为被观察者需要通知观察者,而观察者可能需要从被观察者中获取数据。
  • MVC架构:控制器和视图之间可能存在双向耦合,因为控制器需要更新视图,而视图可能需要通知控制器某些事件。

如何避免双向耦合

为了避免双向耦合,可以采用以下策略:

  1. 使用接口和抽象类:通过引入接口或抽象类,减少具体类之间的直接依赖。例如,使用观察者模式时,可以通过引入一个抽象的观察者接口,避免被观察者和具体观察者之间的双向耦合。
class Observer
{
public:
    virtual void update() = 0;
};
class Subject
{
    std::vector<Observer *> observers;

public:
    void attach(Observer *observer) { observers.push_back(observer); }
    void notify()
    {
        for (Observer *observer : observers)
        {
            observer->update();
        }
    }
};
  1. 依赖注入:使用依赖注入将依赖关系注入类中,而不是在类内部创建依赖对象。这样可以减少类之间的耦合,并提高可测试性。
class Service
{
};
class Client
{
    Service *service;

public:
    Client(Service *service) : service(service) {}
};
  1. 解耦模式:采用设计模式如中介者模式(Mediator Pattern),通过一个中介者来管理类之间的交互,避免直接的双向依赖。
class Mediator
{
    A *a;
    B *b;

public:
    void setA(A *a) { this->a = a; }
    void setB(B *b) { this->b = b; }
    void communicate()
    {
        a->action();
        b->response();
    }
};
  1. 使用智能指针和弱指针:在现代C++中,使用std::shared_ptr和std::weak_ptr可以有效管理对象的生命周期,避免循环引用引发的资源泄漏问题。
class A;
class B
{
    std::weak_ptr<A> a;

public:
    void setA(std::shared_ptr<A> a) { this->a = a; }
};
class A
{
    std::shared_ptr<B> b;

public:
    void setB(std::shared_ptr<B> b) { this->b = b; }
};
  1. 模块化设计:尽量将功能分解为独立的模块,降低类之间的耦合度,增强代码的可维护性和扩展性。

双向耦合的合理应用

尽管双向耦合有很多潜在的缺点,但在某些情况下,合理使用双向耦合是可以接受的。例如,当两个类之间确实存在强关联关系,并且这种关系不会导致复杂度显著增加时,双向耦合可能是最自然的设计。
此外,在一些框架或设计模式中,双向耦合也是不可避免的,特别是在那些需要频繁交互的对象之间。例如,GUI应用中的事件驱动设计,双向耦合可能是不可避免的。

总结

C++中的双向耦合虽然在某些情况下是必要的,但它也可能带来诸多复杂性。通过合理设计类的依赖关系,使用接口、抽象类、依赖注入以及设计模式等技术,可以有效减少双向耦合对系统带来的负面影响。最终,理解双向耦合的本质,并在设计中有意识地避免不必要的耦合,是提升代码质量和系统可维护性的关键。


教程 | vscode ssh远程linux开发c/c++界面程序


背景

最近又要处理统信的适配,现在的场景就是把代码放到实体机上进行编译,每次都得来回跑。。。
最近捣鼓了一下新方式进行处理,使用vscode作为代码编辑通过ssh链接linux机器,然后运行软件用x11的形式回显,这样就能在自己的机器上完美的远程编译与测试。x server在windows上的显示建议用VcXsrv,因为Xming这个东西对GLX支持的不怎么好,运行glxgears会黑屏。

环境

  1. 一台被远程的Linux机器,并且开启ssh服务
  2. 编辑器软件:vscode
  3. XServer服务: VcXsrv 或者是 Xming(还需要安装Xming-fronts才能运行)

本地vcxsrv-64.1.20.14.0.installer.zip

安装步骤

ssh免密登录

在windows中按下win+R快捷键进入cmd命令框中运行命令ssh-keygen
会在C:\Users\Administrator\.ssh文件夹下生成了两个文件

id_rsa.pub # 公钥
id_rsa # 私钥

登录服务器,在用户的.ssh目录下生成authorized_keys,该文件包含一系列允许登录该服务器的公钥,没有就新建该文件。将公钥内容追加到~/.ssh/authorized_keys

echo "公钥内容" >> ~/.ssh/authorized_keys

并在配置文件C:\Users\Administrator\.ssh\config中的对应主机下添加如下内容:

IdentityFile "C:\Users\Administrator\.ssh\id_rsa"//设置私钥路径

vscode 安装插件

本地安装【Remote - SSH】插件。
ssh连接服务器安装【Remote X11】和【Remote X11 (ssh)】两个插件。

配置

并在配置文件C:\Users\Administrator\.ssh\config中的对应主机下添加如下内容:

ForwardX11 yes
ForwardX11Trusted yes
ForwardAgent yes

本地安装X11服务

下载完VcXsrv直接安装就行,一路下一步。
安装完之后点击XLaunch.exe启动,会弹出一个配置界面。
选中Multiple windows后填写视窗ID,这里填的是0(根据自己的情况而定)下一步
CIOMP_2024-06-18_16-19-59.png
选择程序启动方式,默认就行,下一步。
CIOMP_2024-06-18_16-55-45.png
这里将禁用访问控制的配置项勾上,然后下一步,再就是完成,会在右下角留下X的图标,等待客户端的连接。
CIOMP_2024-06-18_16-58-25.png

putty的X11显示程序

配置在Connection->SSH->X11里,这里勾选Enable X11 forwaring,X display Location这个填localhost:11.0,默认转发到本地是localhost:0.0(啥也不填的情况)(会自动设置服务器的DISPLAY值),如果想修改其他端口也可以。
CIOMP_2024-06-20_20-16-10.png
配置好后连接远程linux服务器。

注:这里需要注意一下,linux的端口是putty自动设置的,会将linux设置的端口转发成本地设置端口,所以XServer的端口应与putty内设置的端口一致。

测试一下:

ciomp@ciomp-PC:~$ echo $DISPLAY
localhost:10.0
ciomp@ciomp-PC:~$ xclock
Warning: Missing charsets in String to FontSet conversion

显示效果如下:
CIOMP_2024-06-20_20-07-00.png

vscode的X11显示程序

remote x11插件配置display端口为11(根据本地XServer开启的端口一致),其他默认即可,如下:
CIOMP_2024-06-20_20-26-54.png

打开一个测试程序,配置好launch.json文件直接F5运行,效果如下
CIOMP_2024-06-20_20-31-43.png

直接在vscode终端执行测试如下:
CIOMP_2024-06-22_09-26-04.png

直接在vscode终端执行测试2如下:
CIOMP_2024-09-15_17-12-12.png

如果配置正确,但是连接出现错误的话(比如连了一宿)可以ctrl+shift+p调出命令输入框执行Remote X11: Reconnect Display

VSCode Server 下载时间过长问题

正常通过ssh连接的时候vscode会让远程机器下载VSCode Server,如果未联网或者下载慢请按如下操作:

打开VScode的帮助-关于(help-about)模块,出现Version,Commit,Data,Electron等信息,其中最关键的是Commit后面的字符串,被称为Commit ID,其是由40位十六进制字符串,利用Commit ID下载VScode Server,文件为vscode-server-linux-x64.tar.gz,下载链接有两个

https://update.code.visualstudio.com/commit:${commit_id}/server-linux-x64/stable

https://vscode.download.prss.microsoft.com/dbazure/download/stable/${commit_id}/vscode-server-linux-x64.tar.gz 

注意把:${commit_id}替换成对应的Commit ID

下载后将vscode-server-linux解压到连接用户的目录下的.vscode-server/bin/${commit_id}下,具体操作如下:

# 1 在/home/usr/下新建文件夹.vscode-server/bin/${commit_id}
mkdir -p ~/.vscode-server/bin/${commit_id}
# 2 将vscode-server-linux-x64.tar.gz文件放置在${commit_id}目录下,并进入该文件夹
cd ~/.vscode-server/bin/${commit_id}
# 3 解压文件vscode-server-linux-x64.tar.gz
tar -xvzf vscode-server-linux-x64.tar.gz --strip-components 1
# 4 删除多余文件vscode-server-linux-x64.tar.gz
rm vscode-server-linux-x64.tar.gz
# 5 重启VScode,在安装完成Remote-ssh后,可通过ssh连接远程服务器,此时会生成data,extensions文件夹,可以连接成功,表明VScode-Server安装成功。

C++ | Linux下源码编译VTK6.1


背景

occ需要vtk依赖。

过程

  1. 添加cmake配置,c++标注指定11的版本

CMAKE_CXX_STANDARD 11

  1. 修改CMake文件,让其使用更高版本的gcc
    这边gcc版本为11.4的,所以3或4开头的需要修改11开头

CMake/GenerateExportHeader.cmake内的169行
CMake/vtkCompilerExtras.cmake内的30行
[34]修改成[0-9]+

  1. 修复error: ‘GLintptr’ has not been declared等报错
    /usr/include/GL/glxext.h文件先备份一下

sudo cp /usr/include/GL/glxext.h /usr/include/GL/glxext.h.bak

在此文件的474行添加下边两句话

typedef ptrdiff_t GLsizeiptr;
typedef ptrdiff_t GLintptr;
  1. 编译及安装make -j8 && make install

C++ | window下生成dump文件的死机报告解决程序异常崩溃


代码部分

依赖库 DbgHelp

win下直接链接引入即可: target_link_libraries(${PROJECT_NAME} DbgHelp)

头文件引入

#include <DbgHelp.h>

win异常处理函数注册

#ifdef WIN32
    // 注册异常处理函数
    LPTOP_LEVEL_EXCEPTION_FILTER Top = SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);
    // 此函数与搜狗输入法冲突,会导致编译不过或程序运行崩溃
    // DisableSetUnhandledExceptionFilter();
#endif

异常处理执行函数代码如下

#ifdef WIN32
// 崩溃相关处理函数
LONG WINAPI ApplicationCrashHandler(EXCEPTION_POINTERS *pException){


    //这里弹出一个错误对话框并退出程序
    EXCEPTION_RECORD* record = pException->ExceptionRecord;


    QString errCode(QString::number((uint)record->ExceptionCode,16)),
            errAdr(QString::number(PtrToInt(record->ExceptionAddress),16)), 
            errMod;


    // 崩溃日志写入
    LOG_ERROR(QString::fromLocal8Bit("错误代码:%1 错误地址:%2").arg(errCode).arg(errAdr).toStdString().c_str());


    // 创建错误文件
    HANDLE dumpFile = CreateFile((LPCWSTR)QString("./error.dmp").utf16(),GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(dumpFile != INVALID_HANDLE_VALUE)
    {
        MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
        dumpInfo.ExceptionPointers = pException;
        dumpInfo.ThreadId = GetCurrentThreadId();
        dumpInfo.ClientPointers = TRUE;


        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
        CloseHandle(dumpFile);
        LOG_INFO(QString::fromLocal8Bit("调试信息生成成功。").toStdString().c_str());
    }else{
        LOG_ERROR(QString::fromLocal8Bit("调试信息文件生成失败。").toStdString().c_str());
    }


    QMessageBox::critical(NULL,
                          QString::fromLocal8Bit("程式崩溃"),
                          QString::fromLocal8Bit("<FONT size=4><div><b>对于发生的错误,表示诚挚的歉意</b><br/></div>")+
                          QString::fromLocal8Bit("<div>错误代码:%1</div><div>错误地址:%2</div></FONT>").arg(errCode).arg(errAdr),
                          QMessageBox::Ok);


    //EXCEPTION_EXECUTE_HANDLER
    return EXCEPTION_EXECUTE_HANDLER;
    // return EXCEPTION_CONTINUE_EXECUTION;
}


//防止CRT(C runtime)函数报错可能捕捉不到,这里与搜狗输入法存在bug,回到这程序起不来(暂时不执行此函数)
void DisableSetUnhandledExceptionFilter()
{
    void* addr = (void*)GetProcAddress(LoadLibrary(L"kernel32.dll"), "SetUnhandledExceptionFilter");
    if(addr)
    {
        unsigned char code[16];
        int size = 0;


        code[size++] = 0x33;
        code[size++] = 0xC0;
        code[size++] = 0xC2;
        code[size++] = 0x04;
        code[size++] = 0x00;


        DWORD dwOldFlag, dwTempFlag;
        VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
        WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
        VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
    }
}
#endif

文件处理过程

在程序崩溃后会在指定目录下生成dmp文件(代码内指定了./error.dmp),正常在根目录找到error.dmp文件拖拽到visual studio中,右侧红框内可选择对应pdb进行调试。
65af2c1c92e38.png


教程 | win10上编译安装OpenBLAS库


描述

无。

下载源码

OpenBLAS官网
OpenBLAS开源地址

修改CMakeLists.txt

文件460行添加此代码为编译动态库debug模式的时候添加d后缀

set_target_properties( ${OpenBLAS_LIBNAME}_shared PROPERTIES DEBUG_POSTFIX d)

cmake配置

具体配置如下图:
微信图片编辑_20231101125946.jpg

依次Configure->Generate->Open Porject(选中MinGW的话是没有最后一项的)

visual studio编译安装(会自动识别为动态库)

在生成下拉里选在批生成,勾选INSTALL的Debug和Release两个生成。
屏幕截图 2023-11-01 124757.jpg

MinGW 编译安装(会固定为静态库)

mingw32-make.exe -j8 install

不能调整为动态库具体查看CMakeLists.txt 的53行