我正在为(double
的下/上三角矩阵编写类。利用n*n
三角矩阵仅包含n*(n + 1)/2
[可能非零]个元素这一事实,在内部,我只将一定数量的元素存储在平面数组成员中。
首先,我有一个“普通”(即稠密)矩阵的基类,其中operator()
是一个下标运算符,采用行索引和列索引:
class Matrix {
public:
// [...]
virtual const double &operator()(unsigned i, unsigned j);
virtual double &operator()(unsigned i, unsigned j);
// [...]
private:
std::valarray<double> data_;
std::size_t size_;
}
// [...]
const double &Matrix::operator()(unsigned i, unsigned j) {
return data_[size_*i + j];
}
对于三角矩阵(以下我将以较低的三角矩阵为例),为了提供与常规矩阵相同的接口,我需要实现稍微不同的下标运算符:
const double &LowerTriangular::operator()(unsigned i, unsigned j) const override {
return data_[i*(i + 1)/2 + j];
}
尽管上面的运算符还不完整,因为如果有人要求输入对角线右边(但仍在理论矩阵内)的条目,则会返回另一个(不相关的)元素,但应0
而是返回了。
由于引用不能绑定到局部变量,所以我不能只是return 0
。因此,我该如何实现呢?
我只能提出一个局部静态变量:
const double &LowerTriangular::operator()(unsigned i, unsigned j) const override {
static const double zero = 0;
if (j > i) return zero;
return data_[i*(i + 1)/2 + j];
}
我可以使函数按值返回,但是非const版本呢(当调用者实际上需要修改内容时)?如何确保调用者没有修改zero
静态变量?可以,但是有点难看:
const double &LowerTriangular::operator()(unsigned i, unsigned j) const override {
static double zero = 0;
if (j > i) return zero = 0; // kind of ugly but works
return data_[i*(i + 1)/2 + j];
}
double &LowerTriangular::operator()(unsigned i, unsigned j) override {
return const_cast<double &>( const_cast<const LowerTriangular &>(*this)(i, j) );
}
那么最好的解决方案是什么?
答案 0 :(得分:3)
您选择的优化与您提供的界面冲突。
一种方法可能是不返回引用,而是行为类似于引用的透明包装器。与std::vector::<bool>::reference
类似。一个例子:
struct Reference {
double* element;
operator double() const {
return element
? *element
: 0;
}
Reference& operator=(double d) {
if (!element)
throw std::out_of_range("Cannot modify right side of diagnoal");
*element = d;
return *this;
}
};
const Reference
LowerTriangular::operator()(unsigned i, unsigned j) const {
return {
j > i
? nullptr
: data_ + i*(i + 1)/2 + j
};
}
Reference
LowerTriangular::operator()(unsigned i, unsigned j) {
return {
j > i
? nullptr
: data_ + i*(i + 1)/2 + j
};
}
这与std::vector::<bool>::reference
有相同的警告,即,获取引用地址不会为您提供指向double对象的指针。这可能是使operator&
重载有意义的少数情况之一。但是,当API的用户知道包装器并且确实想要包装器的地址时,这也可能会违反直觉。
代替抛出,您可以指定尝试修改对角线右侧的行为是不确定的。
答案 1 :(得分:-3)
通过常量引用返回时:
在这种情况下,通常通过值返回基本类型(<= 8甚至16个字节)(只读)。 考虑返回 double 而不是 const double&。
通过非恒定引用返回时:
如果超出范围,您可以检查边界并引发异常,或者像std :: vector :: operator []这样的许多标准函数中进行操作:如果超出范围,则这是未定义的行为。只需对其进行记录,以便您的函数的用户知道他必须检查一下自己。
请注意,重载可以具有不同的返回类型,因此在const情况下可以按值返回,在非const情况下可以通过引用返回。
您也可以在类的字段中存储“零”,如果访问矩阵的空半部分,则返回对其的引用。 返回参考之前,您必须将其设置为零,以防万一恶意用户同时对其进行了更改。