gpt4 book ai didi

c++ - 处理用于创建多个版本算法的#ifdef

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:11:22 25 4
gpt4 key购买 nike

我正在尝试对用 C++ 编写的算法的许多(大约 25 个)变体进行基准测试。

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

  1. 复制代码并对复制的版本做小的改动

  2. 子类化基​​础算法类

  3. 使用#ifdef在代码片段之间切换

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

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

#ifdef 将我通过复制代码和子类化创建的六个变体扩展为 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;
}

....

问题在给定配置文件的情况下,允许当前由 #ifdef 指定的算法的多个变体运行的最佳方法是什么指定要运行的算法变体。

理想情况下,我只想编译一次代码并在运行时使用配置文件选择算法变体。

最佳答案

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

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;
}

现在您可以通过此模板参数选择您的行为:

foo<algorithm_type::type_a>(11, 0.1605);
foo<algorithm_type::type_b>(11, 0.1605);
foo<algorithm_type::type_c>(11, 0.1605);

作为常量表达式的类型产生一个编译时决定的分支(也就是说,其他已知为死代码并被删除)。事实上,您的编译器应该就此警告您(如何处理取决于您)。

但是您仍然可以很好地分发运行时值:

#include <stdexcept>

void foo_with_runtime_switch(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");
}
}

foo_with_runtime_switch(algorithm_type::type_a, 11, 0.1605);
foo_with_runtime_switch(algorithm_type::type_b, 11, 0.1605);
foo_with_runtime_switch(algorithm_type::type_c, 11, 0.1605);

算法的内部结构保持不变(消除了死分支,进行了相同的优化),只是实现方式发生了变化。 (请注意,可以概括枚举的想法,以便自动生成此开关;如果您发现自己有一些变体,学习这可能会很好。)

当然你仍然可以#define默认的特定算法:

#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);

所有这些共同为您提供了三者的优势,不再重复。

在实践中,您可以将所有三个都作为重载:一个用于知道在编译时使用哪种算法的用户,那些需要在运行时选择它的用户,以及那些只想要默认值的用户(您可以通过一个项目范围的 #define):

// 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);
}

当然,如果某些实现类型总是比其他实现类型差,请摆脱它。但是,如果您发现有两个有用的实现,那么以这种方式保留它们并没有什么坏处。

关于c++ - 处理用于创建多个版本算法的#ifdef,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15644747/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com