有没有办法提取多个相似函数的外循环?
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()...);
}
现在我们可以重写运算符了:
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 的原因。
示例:我想从这些除一行之外相同的运算符函数中提取嵌套的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()...);
}
现在我们可以重写运算符了:
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 的原因。