减少 C++ 中的代码重复:在略有不同的项目中使用相同的样板代码片段

问题描述

关于我的具体用例的一点:我有一个插件,旨在与 Unreal Engine 项目集成,为了向插件用户演示如何做到这一点,我将它与 Unreal 的一个免费集成可用的示例游戏作为示例。集成是针对游戏的,因为它会修改菜单以允许用户轻松地与我的插件交互。

但是,在理想的世界中,我希望能够:

  1. 提供与多个不同虚幻引擎版本的示例游戏的集成。这至少包括 3 个当前存在的 Unreal 版本(4.24、4.25 和 4.26),但可能会扩展到 N 个不同的未来版本。这实质上使集成代码成为“样板文件”,因为它是每个示例游戏版本中的功能所必需的,但在不同版本之间根本没有变化。
  2. 能够从一个地方维护大部分集成代码。我不想每次更改某些内容时都必须在每个示例游戏集成中进行相同的修改,因为像这样处理多个并行代码库需要大量工作,并且会增加出现错误的可能性。

这几乎是一个可以通过代码补丁解决的问题:无论我使用的是哪个版本的示例游戏,集成代码都适合相同文件中的相同函数/类。但是,示例游戏文件本身的内容在不同引擎版本中并不完全相同,因此说“在此行将这个大块块插入文件中”的补丁并不总是正确的.还有一种理论上的可能性,即将来在示例游戏中引入了更实质性的更改,这可能需要我在这种情况下更改我的集成(尽管这还没有发生 - 次要引擎版本的更改似乎很小)。

解决这个问题的最佳方法是什么?我能想到的一种特别可怕的方法(但一种演示概念的方法)是将集成的每个块分离到一个单独的文件中,然后直接 #include "Chunk1.inc",#include "Chunk2.inc",...进入示例游戏每个版本中的相关类和函数。

void ExistingClass::PerformOperationA()
{
    // ... existing implementation ...

    // Horrible hack:
    #include "IntegrationChunk1.inc"
}

我确信这会编译,但我编写或最终用户阅读并不容易,因为代码本身在像这样的片段中隔离时将缺乏所有上下文。

另一个例子可能是将集成代码的每个部分保存在一个单独的类中,该类被声明为代码通常所在的类的朋友。友元类被赋予一个指向主类的指针,因此可以访问任何所需的私有成员,并具有在适当点调用的相关函数。这种模仿 C# 的 partial 类概念,并且会使原始代码和修改后的代码之间的关系变得明确,因为它都是纯粹、透明的 C++。

例如:

class ExistingClass
{
    // Additional declaration of the friend integration class:
    friend class ExistingClass_Integration;
    ExistingClass_Integration m_Integration;

public:
    ExistingClass() :
        // ... existing initialisers ...
        m_Integration(this)

    void PerformOperationA()
    {
        // ... existing code ...
        
        // Additional bits that need to happen as part of the integration:
        m_Integration.PerformOperationA();
    }
};

class ExistingClass_Integration
{
    ExistingClass* m_Owner = nullptr;

public:
    ExistingClass_Integration(ExistingClass* owner) :
        m_Owner(owner)
    {
    }
    
    void PerformOperationA()
    {
        // Do some things here,potentially using m_Owner->...
    }
};

除了关于这是否是推荐做法的问题外,这里还有一个可读性权衡。尽管集成代码修改的内容定义明确且易于遵循,但它并不能真正代表真实用户将如何与他们的游戏集成 - 他们只是直接编写代码。额外的关节只是为了我自己的维护目的,这可能会使集成看起来比实际需要的更复杂。

这是思考这个问题的正确方法,还是我需要退后一步尝试不同的方法?有人有推荐吗?


本着提供关于我实际使用的代码的更多细节的精神,我的集成包括以下内容:

现有源文件中的额外包含

#include "Engine/GameInstance.h"
#include "Engine/NetworkDelegates.h"
#include "MyPlugin/MyClass.h" // <- NEW

现有类的增强

class ExistingClass
{
public:
    void Function1();
    void Function2();
    
private:
    int m_Var1;
    int m_Var2;
    
// BEGIN INTEGRATION
public:
    void ExtraFunction();
    
private:
    int m_ExtraVar;
// END INTEGRATION
};

挂钩现有功能

此处的集成旨在简单地调用新函数并返回,以尽量减少与现有代码的差异。

void ExistingClass::CreateMainMenu()
{
    // Existing implementation,eg.:
    MainMenuUI = MakeShareable(new FShooterMainMenu());
    MainMenuUI->Construct(this,Player);
    MainMenuUI->AddMenuToGameViewport();

    SetUpExtrasAfterMenuCreated() // <- NEW

    // ... further existing implementation ...
}

解决方法

像这样处理多个并行代码库需要大量工作,并且会增加出现错误的可能性。 .. 解决这个问题的最佳方法是什么?

没有最好的方法。通用修补需要手动工作,并且在一些公司中有专门从事此工作的全职员工。这就是为什么拥有任何产品(软件或其他任何东西,真的)的多个受支持版本需要大量资金的原因。

最好的方法是以最小化支持旧版本的成本的方式编写您的软件。通常,这意味着最小化测试和验证旧版本的成本,而不是自动修补,这在许多情况下根本不可能。有时,如果可以修改基本代码以使其尽可能容易打补丁,那可能会更幸运,但您的情况似乎并非如此。

即使某些案例子集可以自动化,有时出于多种原因,这样做甚至没有意义。其中一些你已经说过:它可能不适用于未来的版本,它可能无法保证可靠,用户不期望这样的代码,等等。

TL;DR:向后移植和维护多个软件分支并不便宜。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...