Как вернуть константу из функции-члена, которая (обычно) возвращает ссылку (на данные-члены) - PullRequest
2 голосов
/ 12 апреля 2019

Я пишу классы для нижних / верхних треугольных матриц (из double s).Используя тот факт, что 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) );
}

Так что же является лучшим решением?

Ответы [ 2 ]

3 голосов
/ 12 апреля 2019

Выбранная вами оптимизация находится в конфликте с предоставленным вами интерфейсом.

Один из подходов может заключаться в том, чтобы не возвращать ссылку, а использовать прозрачную оболочку, которая ведет себя как ссылка.Нечто похожее на 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, то есть получение адреса ссылки не даст вам указатель на двойной объект.Это может быть одним из немногих случаев, когда перегрузка operator& имеет смысл.Но это также может быть нелогичным, когда пользователь API знает об обертке и действительно хочет адрес обертки.

Вместо броска вы можете указать, что пытаетесь изменить правую часть диагоналиявляется неопределенным поведением.

0 голосов
/ 12 апреля 2019

При возврате по постоянной ссылке:

Базовые типы (<= 8 или даже 16 байтов) обычно возвращаются по значению в таких случаях (только для чтения). Попробуйте вернуть <strong>double вместо const double & .

При возврате по непостоянной ссылке:

Вы можете проверить границы и выбросить исключение, если доступ осуществляется за пределами, или сделать так, как во многих стандартных функциях, таких как std :: vector :: operator []: если доступ осуществляется за пределами, то это неопределенное поведение. Просто запишите это так, чтобы пользователь вашей функции знал, что он должен проверить себя.

Обратите внимание, что перегрузки могут иметь разные типы возвращаемых значений, поэтому вы можете возвращать по значению в случае с константой и по ссылке в случае без учета констант.

Вы также можете хранить «ноль» в поле вашего класса и возвращать ссылку на него при доступе к нулевой половине матрицы. Вам придется установить его на ноль, прежде чем возвращать ссылку, на случай, если злоумышленник изменит его за это время.

...