处理#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在代码片段之间切换#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