【问题标题】:Is there a way to extract outer loops of multiple similar functions?有没有办法提取多个相似函数的外循环?
【发布时间】:2021-12-30 22:38:57
【问题描述】:

示例:我想从这些除了一行之外相同的运算符函数中提取嵌套的 for 循环。

// Add two matrices
Matrix& operator+=(const Matrix& other)
{
    for (int i = 0; i < this->m_rows; i++)
    {
        for (int j = 0; j < this->m_cols; j++)
        {
            (*this)(i, j) = (*this)(i, j) + other(i, j); // Only difference
        }
    }
    return *this;
}

// Subtract two matrices
Matrix& operator-=(const Matrix& other)
{   
    for (int i = 0; i < this->m_rows; i++)
    {
        for (int j = 0; j < this->m_cols; j++)
        {
            (*this)(i, j) = (*this)(i, j) - other(i, j); // Only different
        }
    }
    return *this;
}

【问题讨论】:

  • 使用内部私有成员函数,它需要一个 lambda 作为参数?
  • 这是一种访问者模式。

标签: c++ code-duplication


【解决方案1】:

您可以编写一个函数模板来接受二进制函数并将其应用于循环内的所有元素对

template<typename Op>
void loops(const Matrix& other, Op op)
{
    for (int i = 0; i < this->m_rows; i++)
    {
        for (int j = 0; j < this->m_cols; j++)
        {
            (*this)(i, j) = op((*this)(i, j), other(i, j)); 
        }
    }
}

然后像这样使用它

// Add two matrices
Matrix& operator+=(const Matrix& other)
{
    loops(other, std::plus{});
    return *this;
}

// Subtract two matrices
Matrix& operator-=(const Matrix& other)
{   
    loops(other, std::minus{});
    return *this;
}

【讨论】:

    【解决方案2】:

    我认为这类问题通常意味着抽象不佳。

    在示例情况下,高效矩阵将具有单个连续数组,其中 matrix(i,j) 在幕后转换为 array[i*n_columns+j]i,j 接口在许多情况下更简单(否则您只需使用向量),但没有理由限制用户直接访问底层数组元素 - 更不用说在您自己的矩阵类中了!

    另一种说法是,您正在使用为您创建(i,j) 抽象的类,而现在您想要另一层抽象来撤消工作。这在 CPU 时间和您的时间上都是代价高昂的,并且会产生意大利面条式代码。相反,请确保您(和您的用户)可以通过直接元素访问和迭代器访问底层数组:

    public:
    
    auto & operator [] (size_t i)
    {
        return data[i]; // our underlying array
    }
    
    const auto & operator [] (size_t i) const
    {
        return data[i];
    }
    
    Matrix& operator += (const Matrix& other)
    {
        for (size_t i = 0; i < size(); ++i)
            data[i] += other[i];
    
        return *this;
    }
    
    Matrix& operator -= (const Matrix& other)
    {   
        for (size_t i = 0; i < size(); ++i)
            data[i] -= other[i];
    
        return *this;
    }
    

    您可能认为这会以某种方式破坏封装,但事实并非如此。用户对编辑元素具有相同的访问权限,但无权更改矩阵大小或访问原始指针。但是,为了回答您关于避免循环的更一般性问题,我假设我们只能访问迭代器:

    Matrix& operator += (const Matrix& other)
    {
        auto it1 = begin();
        auto it2 = other.begin();
    
        for (size_t i = 0; i < size(); ++i)
            it1[i] += it2[i];
    
        return *this;
    }
    
    Matrix& operator -= (const Matrix& other)
    {   
        auto it1 = begin();
        auto it2 = other.begin();
    
        for (size_t i = 0; i < size(); ++i)
            it1[i] -= it2[i];
    
        return *this;
    }
    

    好的,这样更好,但我们可以为任何迭代器或容器抽象出重复部分,如下所示:

    template <class Func, class ... Its>
    void ForArrays (size_t size, Func&& f, Its && ... its)
    {
        for (size_t i = 0; i < size; ++i)
            f(its[i]...);
    }
    
    template <class Func, class ... Cs>
    void ForContainers (Func&& f, Cs && ... cs)
    {
        size_t size = (cs.size(), ...);
    
        assert(((size == cs.size()) && ...));
    
        ForArrays(size, f, cs.begin()...);
    }
    

    Demo

    现在我们可以重写我们的操作符了:

    Matrix& operator += (const Matrix& other)
    {   
        ForContainers([](auto& lhs, auto rhs){lhs += rhs}, *this, other);
    
        return *this;
    }
    
    Matrix& operator -= (const Matrix& other)
    {   
        ForContainers([](auto& lhs, auto rhs){lhs -= rhs}, *this, other);
    
        return *this;
    }
    

    我认为这里的教训是永远不要撤消抽象的工作;相反,如果它对您的部分代码没有用,那么完全避免它(在代码的那部分)。一个抽象层撤消一个抽象层是一些人远离OOP的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多