动态选择如何将类型与公共接口组合时如何避免代码的指数增长

问题描述 投票:2回答:2

考虑一堆基本类型Foo,所有这些都具有通用方法Bar()的唯一实现。我可以像这样组合Foo1Foo2Foo5

CombinedFoo<Foo1, Foo2, Foo5> combined_foo;

[使用递归继承使CombinedFoo实际上与以下相同:

class CombinedFoo <Foo1, Foo2, Foo5>
{
    Foo1 foo1;
    Foo2 foo2;
    Foo5 foo5;

public:

    void Bar ()
    {
        foo1.Bar();
        foo2.Bar();
        foo5.Bar();
    }
};

这很方便,但是当我想要一个函数在运行时选择将Foo类型组合以发送给函数的函数时遇到问题,例如template <typename Foo> void Do (Foo && foo);。使用ifswitch来解决3个选项版本的示例解决方案:

int result = 0;

if (criteria_for_foo1)
    result += 100;

if (criteria_for_foo2)
    result += 10;

if (criteria_for_foo3)
    result += 1;

switch (result)
{
     case 001 : Do(Foo3());
                  break;

     case 010 : Do(Foo2());
                  break;

     case 011 : Do(CombinedFoo<Foo2, Foo3>());
                  break;

     case 100 : Do(Foo1());
                  break;

     case 101 : Do(CombinedFoo<Foo1, Foo3>());
                  break;

     case 110 : Do(CombinedFoo<Foo1, Foo2>());
                  break;

     case 111 : Do(CombinedFoo<Foo1, Foo2, Foo3>());
                  break;

     default : break; 
}

if语句很好,它们线性增长,但是随着我们有更多选择,switch语句呈指数增长。我的现实问题有4个选项,因此我需要处理16个我不想维护的案例。

我相信无法避免可执行文件呈指数增长,但是有一种方法可以避免代码中出现这种情况(不会在Bar方法中引入明显的低效率 [从编辑添加])?或针对此一般问题是否有已知的解决方法/替代方法?

编辑:

[为清楚起见:Do(Foo1); Do(Foo2);Do(CombinedFoo<Foo1, Foo2>())不同,对于单个调用Foo的情况,将Do组合在一起至关重要。

对于那些讨厌抽象的人,现实世界中的问题是对NP-Hard问题的优化,其中我的Foo实际上是可以编辑我的解决方案的基本GeneratorMove,分成各种Solver,例如SolverSimulatedAnnealing。如果我一次只发送一个生成器,那么我的求解器将进行迭代,使他们花费大部分时间被卡住(众所周知,反复考虑相同类型的移动会产生这种效果)。

我对选项有问题的原因是,根据Solver实例,我的某些Move无法正确使用某些Problem

c++ oop templates interface generic-programming
2个回答
2
投票

不确定这是否是您所需要的,但是呢:

Foo *obj1 = nullptr;
Foo *obj2 = nullptr;
Foo *obj3 = nullptr;
Foo *obj4 = nullptr;

     if (cond1) { obj1 = new Foo1; /* do more stuff here */ }
else if (cond2) { obj2 = new Foo5; /* do more stuff here */ }
else if (cond3) { obj4 = new Foo5; /* do more stuff here */ }
else            { obj3 = new Foo3; /* do more stuff here */ }

然后,调用此函数:

void func(
    Foo *obj1,
    Foo *obj2,
    Foo *obj3,
    Foo *obj4)
{
    if (obj1) obj1->bar();
    if (obj2) obj2->bar();
    if (obj3) obj3->bar();
    if (obj4) obj4->bar();
}

0
投票

这是一个可能的解决方案:

struct DisabledFoo
{
    void Bar() {}
};

template <size_t Combination, typename Disabled, typename... Foos, size_t... Is>
auto FoosFor(std::index_sequence<Is...>)
{
    return CombinedFoo<std::conditional_t<Combination & 1 << (sizeof...(Foos) - 1 - Is), Foos, Disabled>...>{};
}

template <typename F, size_t Combination, typename Disabled, typename... Foos>
void FooDo(const F& func) {
    func(FoosFor<Combination, Disabled, Foos...>(std::make_index_sequence<sizeof...(Foos)>()));
}

template <typename F, typename Disabled, typename... Foos, size_t... Combinations>
constexpr auto MakeFooDoers(std::index_sequence<Combinations...>) {
    return std::array{ FooDo<F, Combinations, Disabled, Foos...>... };
}

constexpr size_t constexpr_pow(size_t base, size_t exp)
{
    if (exp == 0) {
        return 1;
    }
    size_t ret = base;
    for (size_t i = 0; i < exp - 1; ++i) {
        ret *= base;
    }
    return ret;
}

template <typename F, typename Disabled, typename... Foos>
constexpr std::array FooDoers = MakeFooDoers<F, Disabled, Foos...>(std::make_index_sequence<constexpr_pow(2, sizeof...(Foos))>());

template <typename F>
void DoCombination(size_t combination, const F& func) {
    FooDoers<
        F,
        DisabledFoo,
        Foo1,
        Foo2,
        Foo3
    >[combination](func);
}

称呼为

DoCombination(0b111, [](auto foo) { Do(foo); });

Live Demo

这避免了尽可能多的运行时开销,仅执行一个间接调用即可。它通过构建constexpr函数指针数组来工作,每个函数指针均分派到不同的CombinedFoo,其中虚拟实现DisabledFoo用于我们实际上不想调用的Foos。

基本上,FooDoers最终看起来像这样:

constexpr std::array FooDoers = {
    [] { Do(CombinedFoo<DisabledFoo, DisabledFoo, DisabledFoo>{}); },
    [] { Do(CombinedFoo<DisabledFoo, DisabledFoo, Foo3>{});        },
    [] { Do(CombinedFoo<DisabledFoo, Foo2,        DisabledFoo>{}); },
    [] { Do(CombinedFoo<DisabledFoo, Foo2,        Foo3>{});        },
    [] { Do(CombinedFoo<Foo1,        DisabledFoo, DisabledFoo>{}); },
    [] { Do(CombinedFoo<Foo1,        DisabledFoo, Foo3>{});        },
    [] { Do(CombinedFoo<Foo1,        Foo1,        DisabledFoo>{}); },
    [] { Do(CombinedFoo<Foo1,        Foo2,        Foo3>{});        }
};

然后我们将其编入索引并调用正确的包装器。

要添加另一个Foo类,只需将其作为另一个参数添加到FooDoers中的DoCombination

© www.soinside.com 2019 - 2024. All rights reserved.