编写一个可能返回一个或多个值的函数

时间:2017-11-08 15:02:21

标签: c++ idioms

假设我想编写一个函数,比如说,在某个范围内返回x的f(x)之和。

double func() {
    double sum = 0.;
    for (int i=0; i<100; i++) {
        sum += f(i);
    }
    return sum;
}

但有时,除了最后的总和,我还需要部分术语,所以我可以做到

pair<vector<double>,double> func_terms() {
    double sum = 0.;
    vector<double> terms(100);
    for (int i=0; i<100; i++) {
        terms[i] = f(i);
        sum += terms[i];
    }
    return {terms, sum};
}

问题是,这是代码重复。在这个例子中,这看起来非常无害,但是假设函数更大(这种情况促使我问这个),并且两个版本的区别在于少数几行(在这个例子中,逻辑是同样只有后一个版本在添加到sum之前将该术语存储在向量中,并返回一个带有该向量的对;任何其他逻辑都是等价的)。然后我将不得不编写和维护两个几乎完全相同的函数版本,只有几行和return语句不同。我的问题是,是否有成语/模式/最佳实践来处理这类问题。可以让我分享两个版本之间的公共代码。

简而言之:我可以编写两个函数,并且必须维护两个几乎完全相同的版本。或者我可以使用后者但是每当我需要总和时这将是非常浪费的,这是不可接受的。处理这个问题的最佳模式是什么?

我认为使用C ++ 17可以做类似

的事情
template<bool partials>
double func(vector<double>* terms=nullptr) {
    double sum = 0.;
    if constexpr (partials)
        *terms = vector<double>(100);
    for (int i=0; i<100; i++) {
        if constexpr (partials) {
            (*terms)[i] = f(i);
            sum += (*terms)[i];
        } else {
            sum += f(i);
        }
    }
    return sum;
}

除了使用指针之外,它与我的意图非常接近(我不能使用引用,因为terms可能是空的)。

6 个答案:

答案 0 :(得分:2)

你的问题标题是“编写一个可能返回一个或多个值的函数”,但它不止于此;正如您的示例所示,该函数在返回结果之前很久也会执行许多不同的操作。这个广泛的问题确实没有通用的解决方案。

但是,对于具体案例,您已经解释过我想提供一种低技术解决方案。您可以根据第三个函数简单地实现这两个函数,并为第三个函数提供一个参数来确定是否执行了额外的功能。

这是一个C ++ 17示例,其中第三个函数被称为func_impl,并且或多或少隐藏在命名空间内,以使func和{{1}的客户端更轻松}:

func_terms

是否低技术取决于您并且取决于您的确切问题。

答案 1 :(得分:1)

这是一个解决方案,建议将一个可选指针传递给vector,并仅在存在时填充它。我将其删除,因为其他答案也提到了它,后一种解决方案看起来更优雅。

您可以将计算抽象为迭代器,因此调用者仍然非常简单,并且不会复制任何代码:

auto make_transform_counting_iterator(int i) {
    return boost::make_transform_iterator(
            boost::make_counting_iterator(i),
            f);
}

auto my_begin() {
    return make_transform_counting_iterator(0);
}

auto my_end() {
    return make_transform_counting_iterator(100);
}

double only_sum() {
    return std::accumulate(my_begin(), my_end(), 0.0);
}

std::vector<double> fill_terms() {
    std::vector<double> result;
    std::copy(my_begin(), my_end(), std::back_inserter(result));
    return result;
}

答案 2 :(得分:0)

一种简单的方法是编写一个通用函数并使用输入参数来做条件。像这样:

double logic(vector<double>* terms) {
    double sum = 0.;
    for (int i=0; i<100; i++) {
        if (terms != NULL) {
            terms.push_back(i);
        }
        sum += terms[i];
    }
    return sum;
}

double func() {
    return logic(NULL);
}


pair<vector<double>,double> func_terms() {
    vector<double> terms;
    double sum = logic(&ret);
    return {terms, sum};
}

此方法用于许多条件。逻辑可能非常复杂并且有许多输入选项。您可以通过不同的参数使用相同的逻辑。 但在大多数情况下,我们不需要那么多的返回值,只需要不同的输入参数。

答案 3 :(得分:0)

如果你不喜欢:

std::pair<std::vector<double>, double> func_terms() {
    std::vector<double> terms(100);

    for (int i = 0; i != 100; ++i) {
        terms[i] = f(i);
    }
    return {terms, std::accumulate(terms.begin(), terms.end(), 0.)};
}

然后可能:

template <typename Accumulator>
Accumulator& func_helper(Accumulator& acc) {
    for (int i=0; i<100; i++) {
        acc(f(i));
    }
    return acc;
}

double func()
{
    double sum = 0;
    func_helper([&sum](double d) { sum += d; });
    return sum;    
}

std::pair<std::vector<double>, double> func_terms() {
    double sum = 0.;
    std::vector<double> terms;

    func_helper([&](double d) {
        sum += d;
        terms.push_back(d);
    });

    return {terms, sum};
}

答案 4 :(得分:0)

我制定了一个OOP解决方案,其中基类总是计算总和并使当前术语可用于派生类,这样:

class Func
{
public:
    Func() { sum = 0.; }
    void func()
    {
        for (int i=0; i<100; i++)
        {
            double term = f(i);
            sum += term;
            useCurrentTerm(term);
        }
    }
    double getSum() const { return sum; }

protected:
    virtual void useCurrentTerm(double) {} //do nothing

private:
    double f(double d){ return d * 42;}
    double sum;
};

因此派生类可以实现虚方法并设置额外的属性(除了sum):

class FuncWithTerms : public Func
{
public:
  FuncWithTerms() { terms.reserve(100); }
  std::vector<double> getTerms() const { return terms; }

protected:
  void useCurrentTerm(double t) { terms.push_back(t); }

private:
  std::vector<double> terms;
};

如果一个人不想公开这些类,可以回退到函数并将它们用作façade(但两个函数,但现在非常易于管理):

double sum_only_func()
{
  Func f;
  f.func();
  return f.getSum();
}

std::pair<std::vector<double>, double> with_terms_func()
{
  FuncWithTerms fwt;
  fwt.func();
  return { fwt.getTerms(), fwt.getSum() };
}

答案 5 :(得分:0)

对于这种情况,最简单的解决方案我认为是这样的:

double f(int x) { return x * x; }

auto terms(int count) {
    auto res = vector<double>{};
    generate_n(back_inserter(res), count, [i=0]() mutable {return f(i++);});
    return res;
}

auto func_terms(int count) {
    const auto ts = terms(count);
    return make_pair(ts, accumulate(begin(ts), end(ts), 0.0));
}

auto func(int count) {
    return func_terms(count).second;
}

Live version

但是这种方法为原始版本提供了func()不同的性能特征。目前的STL有很多方法,但这突出了STL不适用于可组合性的区域。 Ranges v3库提供了一种更好的方法来为这类问题编写算法,并且正在为将来的C ++版本进行标准化。

通常,可组合性/重用与最佳性能之间存在权衡。最好的C ++让我们吃蛋糕并吃掉它但是这是一个例子,正在进行工作以提供标准的C ++更好的方法来处理这种情况。