C++模板滥用问题

3

我整理了两个模板特化使用的代码片段,我认为它们特别奇怪,甚至可以说有些花哨。总的来说,我对使用模板来设计这些对象是否真的是最好的方式表示怀疑(尤其是第一个案例)。

哪种方法更好?为什么?或者是否有完全不同的方法更好?

1)将模板作为替代传递指向函数的指针:

//fusion_manager.h
template <typename InputFilterAlgorithm,
          typename PredictionAlgorithm,
          typename AssociationAlgorithm,
          typename FusionAlgorithm>
class FusionManager
{
public:
    FusionManager(Environment& env);
    ...
private:
    Environment& env_m;
    InputFilterAlgorithm filter_alg_m;
    PredictionAlgorithm prediction_alg_m;
    AssociationAlgorithm association_alg_m;
    FusionAlgorithm fusion_alg_m;
    ...
};

//fusion_manager.cpp
template <typename InputFilterAlgorithm,
          typename PredictionAlgorithm,
          typename AssociationAlgorithm,
          typename FusionAlgorithm>
FusionManager<InputFilterAlgorithm,
              PredictionAlgorithm,
              AssociationAlgorithm,
              FusionAlgorithm,
              TrackExtendedDataType>::FusionManager(Environment& env)
        : 
          env_m(env),
          filter_alg_m(env),
          prediction_alg_m(env),
          association_alg_m(env),
          fusion_alg_m(env)
{
    ...
}

//main.cpp
...
FusionManager<TestInputFilterAlgorithm,
              TestPredictionAlgorithm,
              TestAssociationAlgorithm,
              TestFusionAlgorithm> fusionManager(env);
...

...不要使用这样的方式:

//fusion_manager.h
class FusionManager
{
public:
  //Let's say each algorithm is encapsulated by a class
  FusionManager(Environment& env,
          InputFilterAlgorithm&&,
                 PredictionAlgorithm&&,
                 AssociationAlgorithm&&,
                 FusionAlgorithm&&);
private:
  Environment& env_m;
    InputFilterAlgorithm filter_alg_m;
    PredictionAlgorithm prediction_alg_m;
    AssociationAlgorithm association_alg_m;
    FusionAlgorithm fusion_alg_m;
};

//fusion_manager.cpp
FusionManager::FusionManager(Environment& env, 
                     InputFilterAlgorithm&& filter_alg,
                     PredictionAlgorithm&& prediction_alg,
                     AssociationAlgorithm&& association_alg,
                     FusionAlgorithm&& fusion_alg)
          :
              env_m(env),
              filter_alg_m(std::move(filter_alg)),
              prediction_alg_m(std::move(prediction_alg)),
              association_alg_m(std::move(association_alg)),
              fusion_alg_m(std::move(fusion_alg))
{
  ...
}

//main.cpp
...
FusionManager<TestInputFilterAlgorithm,
            TestPredictionAlgorithm,
            TestAssociationAlgorithm,
            TestFusionAlgorithm> fusionManager(env);
...

2) 使用模板替代继承和虚方法:

//factorization.h
template<typename ProbabilityDistributionType>
class Factorization
{
...
public:
    ProbabilityDistributionType factorize();
private:
    std::Vector<ProbabilityDistributionType> factors_m;
...
};

//factorization.cpp
template<>
CPD Factorization<CPD>::factorize()
{
    for (auto & factor : factors_m)
    {
        factor.factorize();//This will call the factorize method of CPD
    }
}

template<>
JointProbDistr Factorization<JointProbDistr>::factorize()
{
    for (auto & factor : factors_m)
    {
        factor.factorize();//This will call the factorize method of JointProbDistr
    }
}

不要使用像这样的东西:

//factorization.h
template<typename ProbabilityDistributionType>
class Factorization
{
...
public:
    virtual ProbabilityDistributionType factorize() = 0;
private:
    std::Vector<ProbabilityDistributionType> factors_m;
...
};


//cpd_factorization.h
class CPDFactorization : public Factorization<CPD>
{
...
public:
    CPD factorize();//Implementing the parent's pure virtual method. This will call the factorize method of CPD
};

//jointprobdistr_factorization.h
class CPDFactorization : public Factorization<JointProbDistr>
{
...
public:
    JointProbDistr factorize();//Implementing the parent's pure virtual method. This will call the factorize method of JointProbDistr
};

3
需要翻译的内容:you have to consider that not always templates and inheritance with virtual methods are alternatives, because one is happening at compile time and the other at runtime.你需要考虑到,并不是总是可以用模板和虚函数继承来替代,因为其中一个发生在编译时,而另一个发生在运行时。 - 463035818_is_not_a_number
你的第一个观点,是指使用接口而不是指针吗? - SirGuy
@tobi303 谢谢,我知道它们并不总是可以互换,但我遇到了很多情况它们是可以互换的。 - alex
在第一种情况下,我使用复制语义来传递函数对象,但您可以轻松地将它们视为仅传递函数指针。 - alex
2个回答

4
使用模板既有优点,也有缺点。优点是编译器能看到在模板使用时的完全专业化实现。这是一个优点,因为它允许编译器更加积极地优化:它可以内联任何函数调用之间的数据移动,并消除程序员为了方便自己的源代码结构而引入的所有复杂性。但这也是一个缺点,因为它强制模板决策成为编译时决策。如果一个变量是模板参数,它不能依赖于运行时给定的输入。想象一下,如果 std::string 的长度是一个模板参数会发生什么:字符串操作就像 FORTRAN 一样灵活。你绝对不希望这种情况发生。当然,有些东西应该在编译时知道,并且把它们作为模板参数也没问题。但如果你过度使用模板,你将得到不必要的刻板代码。(曾经遇到过,学会了避免它。)另一个缺点是当模板的实现发生变化时,它强制重新编译所有使用模板的内容。如果你的程序都是模板,除了 main 函数以外,每次小小的变化都需要重新编译所有内容。如果你使用函数指针、虚函数和/或函数对象,在被调用的函数的实现发生变化时,调用方就不需要重新编译了。在适当设置的构建系统中,它们将依赖于头文件,只有当存在破坏接口的更改时才会更改。对我来说,这意味着所有的模板都应该是小而自包含的代码片段,不依赖于其他层次的模板。总之,模板是一个很棒的工具,同时也是一个非常容易被过度使用的工具。如果你没有从中获得任何真正的优势,就尽量不要使用它们。

1
谢谢。我有一种印象,使用复杂的模板结构似乎很流行。例如,当你看Eigen库时,你会发现它大量使用模板,使得代码几乎无法阅读。同时,它是其领域中最常用和受人尊敬的库之一,因此我认为这必须是“正确的”做法。 - alex
@alex 是的,我有同样的印象:有相当多的人似乎对在各个地方使用模板感到狂热,而其中许多人似乎在标准库的上下文中工作。我真的不明白他们如何能以这种令人难以置信的水平继续下去,在这个基础上基本上实现了另一种语言,并且从来没有考虑过添加另一个模板层。我的经验告诉我不要对与编程有关的任何事情感到狂热,所以我避免使用过度依赖模板的库。 - cmaster - reinstate monica

3
第一个可以与任何可调用的东西一起使用 - 函数指针、std::function等。
您的建议在类型方面非常有限。
第二个避免了虚拟调用,在需要避免它的情况下很有用,并提供更多内联的机会。
简而言之:第一个使用模板以获得灵活性,第二个则是为了性能。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接