有没有办法提取多个相似函数的外循环?

Is there a way to extract outer loops of multiple similar functions?

示例:我想从这些除一行之外相同的运算符函数中提取嵌套的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;
}

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

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;
}

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

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

另一种表达方式是,您正在使用为您创建 (i,j) 抽象的 classes,现在您需要另一个抽象层来撤消工作。这在 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 的原因。