处理#ifdef和#x27;用于创建算法的多个版本的 我试图用C++来计算一个算法的许多(大约25个)变化。p>

处理#ifdef和#x27;用于创建算法的多个版本的 我试图用C++来计算一个算法的许多(大约25个)变化。p>,c++,configuration,c-preprocessor,C++,Configuration,C Preprocessor,我使用三种方法的组合实现了这些变化: 复制代码并对复制的版本进行微小更改 子类化基本算法类 使用#ifdefs在代码片段之间切换 选项1和2产生的变化是可以的,因为我可以选择在配置文件中运行哪种算法变化。然后,我可以遍历不同的配置文件并保存“配置:结果”对的记录——保存这些记录对我的工作非常重要 我目前在#ifdef上遇到了一个问题,因为我必须编译多个版本的代码来访问这些变体,这使得运行自动实验脚本和保留结果的准确记录变得更加困难。然而,#ifdefs非常有用,因为如果我在一份代码中发现错误,那

我使用三种方法的组合实现了这些变化:

  • 复制代码并对复制的版本进行微小更改

  • 子类化基本算法类

  • 使用
    #ifdef
    s在代码片段之间切换

  • 选项1和2产生的变化是可以的,因为我可以选择在配置文件中运行哪种算法变化。然后,我可以遍历不同的配置文件并保存“配置:结果”对的记录——保存这些记录对我的工作非常重要

    我目前在
    #ifdef
    上遇到了一个问题,因为我必须编译多个版本的代码来访问这些变体,这使得运行自动实验脚本和保留结果的准确记录变得更加困难。然而,
    #ifdef
    s非常有用,因为如果我在一份代码中发现错误,那么我就不必记得在多份代码中纠正这个错误

    #ifdef
    s将我通过复制代码和子类化创建的六个变体扩展为24个总变体(每个基本变体4个变体)

    下面是一个示例-我主要使用
    #ifdef
    来避免复制过多的代码:

        ....
    
        double lasso_gam=*gamma;
        *lasso_idx=-1;
        for(int aj=0;aj<(int)a_idx.size();aj++){
            int j=a_idx[aj];
            assert(j<=C*L);
            double inc=wa[aj]*(*gamma)*signs[aj];
            if( (beta_sp(j)>0 && beta_sp(j)+inc<0)
    #ifdef ALLOW_NEG_LARS
                || (beta_sp(j)<0 && beta_sp(j)+inc>0)
    #else
                || (beta_sp(j)==0 && beta_sp(j)+inc<0)
    #endif
                ){
                double tmp_gam=-beta_sp(j)/wa[aj]*signs[aj];
    
                if(tmp_gam>=0 && tmp_gam<lasso_gam) {
                    *lasso_idx=aj;
                    *next_active=j;
                    lasso_gam=tmp_gam;
                }
            }
        }
    
        if(lasso_idx>=0){
            *gamma=lasso_gam;
        }
    
        ....
    
    。。。。
    双套索_gam=*伽马;
    *套索idx=-1;
    对于(int aj=0;aj如果你的
    #如果
    分散在各处,在这里或那里更改一行代码,然后根据传递到要运行的变量的函数中的枚举将所有
    #如果
    转换为
    If
    s,并希望编译器在优化方面做得很好。希望它能生成与def几乎相同的代码多次定义函数,但只有一个运行时条件决定运行哪个。无承诺


    如果你是
    #如果
    在算法中使用一块代码,将算法分割成更小的函数,整个算法的不同实现可以调用。如果你的
    #如果
    太过侵入性,以至于你最终会得到50个函数,这显然是不切实际的在具有相同接口的类中,可以使用该算法将它们作为模板参数传递到位置

    class foo {
    public:
      void do_something() {
        std::cout << "foo!" << std::endl;
      }
    }
    
    class bar {
    public:
      void do_something() {
        std::cout << "bar!" << std::endl;
    }
    
    template <class meh>
    void something() {
      meh algorithm;
      meh.do_something();
    }
    
    int main() {
      std::vector<std::string> config_values = get_config_values_from_somewhere();
      for (const austo& config : config_values) { // c++11 for short notation
        switch (config) {
          case "foo":
            something<foo>();
            break;
          case "bar":
            something<bar>();
            break;
          default:
            std::cout << "undefined behaviour" << std::endl;
        }
      }
    }
    
    class-foo{
    公众:
    空做某事{
    
    std::cout您没有提到您正在使用的编译器,但是您可以在命令行上为它们中的任何一个设置#defines。在gcc中,您所需要的只是添加
    -D MYTESTFOO
    来定义MYTESTFOO。这将使#定义方法-无需传播代码更改,而且每个测试都会有不同的编译代码,但是应该易于自动化。

    您可以使用(可能附加的)模板参数来扩充算法,如下所示:

    enum class algorithm_type
    {
        type_a,
        type_b,
        type_c
    };
    
    template <algorithm_type AlgorithmType>
    void foo(int usual, double args)
    {
        std::cout << "common code" << std::endl;
    
        if (AlgorithmType == algorithm_type::type_a)
        {
            std::cout << "doing type a..." << usual << ", " << args << std::endl;
        }
        else if (AlgorithmType == algorithm_type::type_b)
        {
            std::cout << "doing type b..." << usual << ", " << args << std::endl;
        }
        else if (AlgorithmType == algorithm_type::type_c)
        {
            std::cout << "doing type c..." << usual << ", " << args << std::endl;
        }
    
        std::cout << "more common code" << std::endl;
    }
    
    所有这些加在一起,使您不必重复这三种方法的优点

    实际上,您可以将这三个作为重载:一个用于知道在编译时使用哪种算法的用户,一个用于需要在运行时选择算法的用户,另一个用于只需要默认值的用户(您可以通过项目范围的
    #define
    )来覆盖它们:

    //foo.hpp
    枚举类算法\u类型
    {
    a型,
    类型_b,
    c型
    };
    //对于那些知道使用哪种算法的人
    模板
    void foo(通常为int,双参数)
    {
    
    std::cout如果您有多个带有
    #ifdef
    s的版本,通常最好构建多个可执行文件,并让您的配置脚本决定在基准测试时运行哪些可执行文件。然后,您在Makefile中有规则来构建各种配置:

    %-FOO.o: %.cc
            $(CXX) -c $(CFLAGS) -DFOO -o $@ $<
    
    %-BAR.o: %.cc
            $(CXX) -c $(CFLAGS) -DBAR -o $@ $<
    
    test-FOO: $(SRCS:%.cc=%-FOO.o)
            $(CXX) $(LDFLAGS) -DFOO -o $@ $^ $(LDLIBS)
    
    %-FOO.o:%.cc
    $(CXX)-c$(CFLAGS)-DFOO-o$@$<
    %-律师事务所:%.cc
    $(CXX)-c$(CFLAGS)-DBAR-o$@$<
    测试FOO:$(SRCS:%.cc=%-FOO.o)
    $(CXX)$(LDFLAGS)-DFOO-o$@$^$(LDLIBS)
    
    一种方法是不在可执行文件中包含预处理器指令,而是这样做:

    #define METHOD METHOD1
    int Method1() { return whatever(); };
    #undef METHOD
    
    #define METHOD METHOD2
    int Method2() { return whatever(); };
    #undef METHOD
    

    假设
    任何
    依赖于
    方法,那么这些方法将产生不同的结果。

    这就是我目前正在考虑的。如果
    处于最内部的循环中,那么我担心性能会受到影响。不过,我想优化级别会有所不同。这并不是真正适用的理想情况下,我希望编译一次并获得由
    ifdef
    s给出的算法的所有版本,然后在运行时根据配置文件选择alg版本。@user1149913:展开您的问题,我没有从中选择。您通过
    ifdef
    定义了多个版本?这是如何工作的?我做了一些编辑-希望我的问题更清楚一点。@user1149913:我觉得你需要改变你的方法,而不是继续沿着这条路走下去。为什么要混合所有这些方法?在过去几个月里,这是一个长期运行的项目,随着新想法的发展,我试图尽可能轻松地实施它们。理想情况下,我能够做到通过子类化来完成所有事情,但尝试这样做太困难了,导致了更大的混乱。我的问题是不必创建大量几乎相同的
    dou\u something
    函数,而不是如何在这些函数中进行选择。我一直在考虑这样做,但这会增加额外的一层与其他解决方案不同的是,这还具有易于测试不同的编译器指令(例如openmp pragmas)以及多种代码选择和预处理器选择的排列的优点。
    #define FOO_ALGORITHM algorithm_type::type_a
    
    void foo_with_define(int usual, double args)
    {
        return foo<FOO_ALGORITHM>(usual, args);
    }
    
    foo_with_define(11, 0.1605);
    
    // foo.hpp
    
    enum class algorithm_type
    {
        type_a,
        type_b,
        type_c
    };
    
    // for those who know which algorithm to use
    template <algorithm_type AlgorithmType>
    void foo(int usual, double args)
    {
        std::cout << "common code" << std::endl;
    
        if (AlgorithmType == algorithm_type::type_a)
        {
            std::cout << "doing type a..." << usual << ", " << args << std::endl;
        }
        else if (AlgorithmType == algorithm_type::type_b)
        {
            std::cout << "doing type b..." << usual << ", " << args << std::endl;
        }
        else if (AlgorithmType == algorithm_type::type_c)
        {
            std::cout << "doing type c..." << usual << ", " << args << std::endl;
        }
    
        std::cout << "more common code" << std::endl;
    }
    
    // for those who will know at runtime
    void foo(algorithm_type algorithmType, int usual, double args)
    {
        switch (algorithmType)
        {
        case algorithm_type::type_a:
            return foo<algorithm_type::type_a>(usual, args);
        case algorithm_type::type_b:
            return foo<algorithm_type::type_b>(usual, args);
        case algorithm_type::type_c:
            return foo<algorithm_type::type_c>(usual, args);
        default:
            throw std::runtime_error("wat");
        }
    }
    
    #ifndef FOO_ALGORITHM
        // chosen to be the best default by profiling
        #define FOO_ALGORITHM algorithm_type::type_b
    #endif
    
    // for those who just want a good default
    void foo(int usual, double args)
    {
        return foo<FOO_ALGORITHM>(usual, args);
    }
    
    %-FOO.o: %.cc
            $(CXX) -c $(CFLAGS) -DFOO -o $@ $<
    
    %-BAR.o: %.cc
            $(CXX) -c $(CFLAGS) -DBAR -o $@ $<
    
    test-FOO: $(SRCS:%.cc=%-FOO.o)
            $(CXX) $(LDFLAGS) -DFOO -o $@ $^ $(LDLIBS)
    
    #define METHOD METHOD1
    int Method1() { return whatever(); };
    #undef METHOD
    
    #define METHOD METHOD2
    int Method2() { return whatever(); };
    #undef METHOD