【问题标题】:How to return a constant from a member function that (usally) returns a reference (to member data)如何从(通常)返回引用(对成员数据)的成员函数返回常量
【发布时间】:2019-09-03 07:21:04
【问题描述】:

我正在为下/上三角矩阵(doubles)编写一个类。通过利用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];
}

我可以让函数按值返回,但是非常量版本(当调用者实际需要修改内容时)呢?如何确保调用者不修改 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) );
}

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

【问题讨论】:

  • 如果有人请求超出范围的数据会被认为是例外吗?如果是这样,您可以抛出异常。
  • 或者考虑使用std::optional
  • 如果保证operator() 的结果可以有意义地赋值,那么j &gt; i 的情况确实是例外,因为它们不会兑现该保证。在这种情况下,正确的做法是抛出异常。如果不是这种情况,则用户负责验证结果是否有效,这开辟了许多可能性。最简单的可能是只返回一个指针,当j &gt; i 时使用nullptr。什么是正确的实际上取决于您打算如何使用您的类型。
  • @Anakhand 您可能需要考虑不返回double &amp;,而是返回一个代理类型,该代理类型的行为类似于对双精度的引用,但会进行坐标检查。一种可隐式转换为 double 并可从 double 分配的类型。编辑:然后您可以以任何您想要的方式处理对不受支持的坐标的分配。虽然我不清楚当有人试图分配一个不属于你三角形的元素时,你希望你的类型做什么。
  • 我希望使用矩阵引用的函数(不知道矩阵是否为三角形)能够正常使用接口。我考虑这种设计目标在某种程度上也很危险。一个函数在上三角形中意外写入值(!= 0),这些值神奇地“消失”,因此读回提供的值与写入的值不同。我会睡得更好,除了对上三角形的写访问,对于写 0.0 的例外情况可能不会抛出。 (...来自异常的例外情况... ;-))

标签: c++ matrix reference containers


【解决方案1】:

当你通过常量引用返回时:

在这种情况下,基本类型(double 而不是 const double&

当你通过非常量引用返回时:

如果访问越界,您可以检查边界并抛出异常,或者像 std::vector::operator[] 等许多标准函数:如果访问越界,则这是未定义的行为。只需将其记录下来,以便您的函数的用户知道他必须检查自己。

请注意,重载可以有不同的返回类型,因此您可以在 const 情况下按值返回,在非 const 情况下按引用返回。

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

【讨论】:

  • 对,我可能应该按值返回 const 版本。但是对于非常量版本,挑战在于我强烈希望接口的行为与一般矩阵完全相同——事实上,这个类是另一个类Matrix 的子类。我希望使用 Matrix 引用的函数(谁不知道矩阵是否为三角形)能够正常使用界面,而在访问对角线右侧的元素时不会引发异常(不是实际上“越界”)。
【解决方案2】:

您选择的优化与您提供的界面冲突。

一种方法可能是不返回引用,而是像引用一样的透明包装器。类似于std::vector::&lt;bool&gt;::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::&lt;bool&gt;::reference 有相同的警告,即获取引用的地址不会给你指向双对象的指针。这可能是重载operator&amp; 有意义的少数情况之一。但是,当 API 的用户知道包装器并且确实想要包装器的地址时,这也可能与直觉相反。

您可以指定尝试修改对角线右侧是未定义行为,而不是抛出。

【讨论】:

  • 需要注意的是,这并不完全是const 正确的。虽然您无法直接分配给operator()(...) const 的结果,但您可以将结果复制到Reference 的非const 实例中,这样您就可以修改引用的元素。典型的解决方案将有两种Reference 类型,一种用于const,另一种用于非const 返回值。
  • 这种类型会与double &amp; 协变吗?需要的原因是operator()Matrix 中相应虚方法的覆盖,它返回double &amp;。 (我没有提到这一点。)
  • @Anakhand 这不会是协变的。如果涉及继承,则必须使层次结构中的所有类都返回包装器,并且要么具有包装器的层次结构,要么让包装器使用回调。
猜你喜欢
  • 2021-08-10
  • 1970-01-01
  • 1970-01-01
  • 2018-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多