如何从成员函数(通常)返回常量(通常返回对成员数据的引用)中返回常量

时间:2019-04-12 13:45:56

标签: c++ matrix reference containers

我正在为(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) );
}

那么最好的解决方案是什么?

2 个答案:

答案 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情况下可以通过引用返回。

您也可以在类的字段中存储“零”,如果访问矩阵的空半部分,则返回对其的引用。 返回参考之前,您必须将其设置为零,以防万一恶意用户同时对其进行了更改。