使用多个 .cpp 文件替代 c++ 中的条件编译

Alternative to conditional compilation in c++, using multiple .cpp files

假设我有一个使用条件编译的 C++ class:

C.hpp

namespace NS{
  class C {
    public:
      C(void);
      ~C(void);
      int func( int arg1 );
    private:
      int memberVar;
}

C.cpp:

#include "C.hpp"

namespace NS{
  C::C( void ){ memberVar = 0; }
  C::~C( void ) {}
  int C::func( int arg1 ){
    int retval = 0;
    memberVar = arg1;
#ifdef DEV_BUILD
    retval = memberVar;
    printf( "memberVar was set.\n" );
#endif
    return retval;
  }
}

(这是一个简化的例子;假设 class C 有几百行,其中一些函数使用条件编译,每个函数使用相同的条件:#ifdef DEV_BUILD.该应用为嵌入式系统应用,条件用于区分测试硬件和生产硬件,有一定差异)

有人建议我改为在单独的 .cpp 文件中实现函数...但我对最好的方法有点犹豫。

我的第一个冲动是创建 C_dev.cpp 和 C_prod.cpp,以不同的方式实现 C::func(),然后编辑 C.cpp:

#include "C.hpp"
#ifdef DEV_BUILD
#include "C_dev.cpp"
#else
#include "C_prod.cpp"
#endif

namespace NS{
  C::C( void ){ memberVar = 0; }
  C::~C( void ) {}
}

...但这是糟糕的风格吗?或者有其他问题?

附加限制:

(更新:建议使用单独的文件,但不是必需的。我愿意接受其他建议)

在没有更多信息的情况下,我建议告诉您使用不同文件的人是在误导您。

让我解决具体问题:...but is this bad style? Or otherwise problematic?

是的,是的。

  • 这与其他人所做的相反,提示您做错了。此外,任何新开发人员都必须了解您的系统的独特之处 "build quirks"。
  • 这意味着您的构建系统/软件存储库要跟踪的文件数量是原来的两倍。
  • 您发现的任何错误都必须在两个文件中修复(这就是我所说的保持文件同步的意思)。
  • 您可能需要一种方法来了解用于构建 exe 的源版本。

要点 1:如果你有数百个小改动散落在一大堆其他方面相同的代码中,请使用条件编译。它通常更容易阅读和维护。

但是如果您在功能级别上细分差异,则可以将不同的功能拆分到不同的实现文件中。通常我使用操作系统包装器来执行此操作,并且在 Linux 和 Windows 上获取目录列表方面的差异非常 coarse-grained。 (注意:此示例已被 C++17 废弃)

要点 2:不要制作多个 header。这只会使问题复杂化。您想要公开两个实现之间的公共接口。如果做不到,那么您已经迷路了,需要另辟蹊径。制作一个适合所有实现的通用 header。

对于这个答案,所有实现都使用 Asker 的原始 header、C.hpp:

namespace NS{
  class C {
    public:
      C(void);
      ~C(void);
      int func( int arg1 );
    private:
      int memberVar;
}

我将 Asker 的示例分成三个 cpp 文件:一个具有跨所有构建的通用功能,两个实现包含差异的功能。这样您就不会重复任何您绝对不需要重复的功能。

C_common.cpp 所有共享功能都在这里

#include "C.hpp"

namespace NS{
  C::C( void ){ memberVar = 0; }
  C::~C( void ) {}
}

C_debug.cpp:带有调试语句的函数放在此处

#include "C.hpp"

namespace NS{
  int C::func( int arg1 ){
    int retval = 0;
    memberVar = arg1;
    retval = memberVar;
    printf( "memberVar was set.\n" );
    return retval;
  }
}

C_no_debug.cpp:没有调试语句的函数放在此处

#include "C.hpp"

namespace NS{
  int C::func( int arg1 ){
    memberVar = arg1;
    return memberVar;
  }
}

当你link程序时,你总是linkC_common并指定C_debug和C_no_debug到link。

您有时可以通过分解复杂函数并仅隔离差异并为差异提供从共享函数中调用的函数来更进一步。

C_common.cpp

#include "C.hpp"

namespace NS{
  C::C( void ){ memberVar = 0; }
  C::~C( void ) {}
  int C::func( int arg1 ){
    memberVar = arg1;
    debugstuff(); // calls the different functionality
    return memberVar;
  }
}

C_debug.cpp:

#include "C.hpp"

namespace NS{
    void debugstuff()
    {
        printf( "memberVar was set.\n" );
    }
}

C_no_debug.cpp:

#include "C.hpp"

namespace NS{
    void debugstuff()
    {
    }
}

这往往很难扩展,因为您可能会得到许多一个线性函数。请参见上面的第 1 点。但是,如果差异的形状恰到好处,您可以利用传递函数参数来减少垃圾邮件。在这种情况下,合乎逻辑的做法是传入调试字符串。一个打印,另一个丢弃。

C_common.cpp

#include "C.hpp"

namespace NS{
  C::C( void ){ memberVar = 0; }
  C::~C( void ) {}
  int C::func( int arg1 ){
    memberVar = arg1;
    debugstuff("memberVar was set.\n"); // calls the different functionality
                                        // and tells it what to do!
    return memberVar;
  }
}

C_debug.cpp:

#include "C.hpp"

namespace NS{
    void debugstuff(const char * message)
    {
        printf( message );
    }
}

C_no_debug.cpp:

#include "C.hpp"

namespace NS{
    void debugstuff(const char * )
    {
    }
}