我认为这类问题通常意味着抽象不佳。
在示例情况下,高效矩阵将具有单个连续数组,其中 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的原因。