При реализации operator [] как включить проверку границ? - PullRequest
4 голосов
/ 22 июня 2009

Прежде всего, я прошу прощения за долгое приведение к такому упрощенному вопросу.

Я реализую класс, который служит очень длинным одномерным индексом на кривой заполнения пространства, или n-кортеж, представляющий декартову координату, которой соответствует индекс.

class curvePoint
{
public:
    friend class curveCalculate;

    //Construction and Destruction
    curvePoint(): point(NULL), dimensions(0) {}
    virtual ~curvePoint(){if(point!=NULL) delete[] point;}

    //Mutators
    void convertToIndex(){ if(isTuple()) calc(this); }
    void convertToTuple(){ if(isIndex()) calc(this); }
    void setTuple(quint16 *tuple, int size);
    void setIndex(quint16 *index, int size);
    void setAlgorithm(curveType alg){algorithm = alg;}

    //Inspectors
    bool isIndex(){return current==Index;}
    bool isTuple(){return current==Tuple;}
    size_t size(){return dimensions;}
    quint16 operator[](size_t index);

    enum curveType{HilbertCurve, ZCurve, GrayCodeCurve};
    enum status{Index, Tuple};

private:
    curveCalculate calc;
    curveType algorithm;
    quint16 *point;
    size_t dimensions;
    status current;
};

(длина массива, на который указывает точка , равна измерения )

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

Я думал о чем-то вроде этого, хотя и реализовано в определении класса:

quint16 curvePoint::operator[](size_t index)
{
    return point[ index % dimensions ];
}

Это делает так, что мы никогда не покидаем границ массива, и если я хорошо документирую, я думаю, что это будет хорошо; тем не менее, я настороженно отношусь к этой конкретной реализации.

Это выглядит приемлемым для других? Есть ли другой способ проверки границ, в то же время удовлетворяя моим ограничениям?

Edit: Расчет таких вещей, как кривые Гильберта и т. Д., Очень запутанный, достаточно запутанный, поэтому я не хочу, чтобы добавлялся дополнительный интерфейс для библиотек stl.

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

Мне скорее нравится идея утверждения; но, если я правильно помню, что перерывы в сборках релизов не так ли?

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

Ответы [ 13 ]

12 голосов
/ 22 июня 2009

Самое простое решение - сделать так, как это делает сам C ++. Это ограничивает количество сюрпризов, которые будут испытывать ваши пользователи.

Сам C ++ довольно последовательный. И встроенные [] для указателей, и std::vector::operator[] имеют неопределенное поведение, если вы используете индекс массива вне границ. Если вы хотите проверить границы, будьте явными и используйте std::vector::at

Следовательно, если вы сделаете то же самое для своего класса, вы можете задокументировать поведение вне границ как «стандартное».

9 голосов
/ 22 июня 2009

В любом случае при реализации Оператор [] мне было интересно, что лучший способ добиться проверки границ является. Я хочу не бросать исключения, если это вообще возможно, и полный диапазон значений можно использовать для каждый номер в массиве так особенный Возвращаемое значение в случае выхода из ошибка границ также невозможна;

Тогда оставшиеся опции:

  • Гибкая конструкция. Что вы сделали. «Исправьте» неверный ввод, чтобы он попытался сделать что-то, что имеет смысл. Преимущество: функция не сбоит. Недостаток: невежественные абоненты, которые получают доступ к элементу вне границ, получат ложь в результате. Представьте себе 10-этажное здание с 1 по 10 этажи:

Вы: «Кто живет на 3 этаже?»

Я: "Мария".

Вы: «Кто живет на 9 этаже?»

Я: "Джо".

Вы: «Кто живет на 1 203-м этаже?»

Я: (Подождите ... 1,203% 10 = 3 ...) > "Мария" .

Вы: "Ого, Мэри должна наслаждаться прекрасными видами с там . Значит, ей тогда принадлежат две квартиры?"

  • A Выходной параметр bool указывает на успех или неудачу. Эта опция обычно заканчивается не очень удобным кодом. Многие пользователи будут игнорировать код возврата. Вы по-прежнему остаетесь с тем, что вы возвращаете в другом возвращаемом значении.

  • Проектирование по контракту. Утверждение, что вызывающий абонент находится в пределах границ. (Для практического подхода в C ++, см. Исключение или ошибка? By Miro Samek или Простая поддержка проектирования по контракту в C ++ Педро Геррейро .)

  • Возврат System.Nullable<quint16>. Ой, подождите, это не C #. Ну, вы могли бы вернуть указатель на квинт16. Это, конечно, имеет много последствий, которые я не буду здесь обсуждать и которые, вероятно, делают эту опцию непригодной для использования.

Мои любимые варианты:

  • Для общедоступного интерфейса общедоступной библиотеки: вход будет проверен, и будет выдано исключение. Вы исключили эту опцию, так что это не вариант для вас. Это все еще мой выбор для интерфейса публично выпущенной библиотеки.
  • Для внутреннего кода: Дизайн по контракту.
7 голосов
/ 22 июня 2009

Для меня это решение неприемлемо, потому что вы можете скрывать очень трудно найти ошибку. Исключение вне диапазона - это путь или, по крайней мере, утверждение в функции.

3 голосов
/ 22 июня 2009

Если вам нужен какой-то "круговой" массив точек, тогда ваше решение в порядке. Однако, для меня это выглядит как сокрытие неправильного использования оператора индексирования за некоторой «безопасной» логикой, поэтому я был бы против предложенного вами решения.

Если вы не хотите разрешать переполнение индекса, вы можете проверить и выдать исключение.

quint16 curvePoint::operator[](size_t index)
{
    if( index >= dimensions)
    {
       throw std::overflow_error();
    }
    return point[ index ];
}

Если вы хотите уменьшить накладные расходы, вы можете избежать исключения, используя утверждения времени отладки (предположим, что предоставленный индекс всегда действителен):

quint16 curvePoint::operator[](size_t index)
{
    assert( index < dimensions);
    return point[ index ];
}

Однако я предлагаю вместо использования точечных и размерных элементов использовать std :: vector для хранения точек. Он уже имеет доступ на основе индекса, который вы можете использовать:

quint16 curvePoint::operator[](size_t index)
{
    // points is declared as std::vector< quint16> points;
    return points[ index ];
}
2 голосов
/ 22 июня 2009

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

1 голос
/ 22 июня 2009

Будь я на вашем месте, я бы последовал примеру, установленному ст.

В этом случае std::vector предоставляет два метода: at, который является проверкой границ, и operator[], который не проверяется. Это позволяет клиенту выбрать версию для использования. Я бы определенно не использовал % size(), так как это просто скрывает ошибку. Однако проверка границ добавит много накладных расходов при итерации по большой коллекции, поэтому она должна быть необязательной. Хотя я согласен с другими авторами, что утверждение является очень хорошей идеей, поскольку это приведет только к снижению производительности в отладочных сборках.

Вам также следует рассмотреть возможность возврата ссылок и предоставления const, а не const версий. Вот объявления функций для std::vector:

reference at(size_type _Pos);
const_reference at(size_type _Pos) const;

reference operator[](size_type _Pos);
const_reference operator[](size_type _Pos) const;

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

1 голос
/ 22 июня 2009

Благодаря комментарию о функции C # в посте Дэниела Даранаса мне удалось выяснить возможное решение. Как я уже сказал в своем вопросе, я использую библиотеки Qt. Там я могу использовать QVariant. QVariant может быть установлен в недопустимое состояние, которое может быть проверено функцией, получающей его. Таким образом, код станет примерно таким:

QVariant curvePoint::operator[](size_t index){
    QVariant temp;
    if(index > dimensions){
        temp = QVariant(QVariant::Invalid);
    }
    else{
        temp = QVariant(point[index]);
    }

    return temp;
}

Конечно, это может привести к добавлению в функцию небольшого количества накладных расходов, поэтому другая возможность - использовать шаблон пары.

std::pair<quint16, bool> curvePoint::operator[](size_t index){
    std::pair<quint16, bool> temp;
    if(index > dimensions){
        temp.second = false;
    }
    else{
        temp.second = true;
        temp.first = point[index];
    }
    return temp;
}

Или я мог бы использовать QPair, который имеет точно такую ​​же функциональность и сделал бы так, чтобы STL не нужно было связывать.

1 голос
/ 22 июня 2009

Лучший способ добиться проверки границ - добавить assert.

quint16 curvePoint::operator[](size_t index)
{
    assert(index < dimensions);
    return point[index];
}

Если ваш код уже зависит от библиотек Boost, вы можете использовать BOOST_ASSERT.

0 голосов
/ 08 августа 2009

Другой вариант - разрешить абоненту выбирать политику за пределами допустимого. Рассмотрим:

template <class OutOfBoundsPolicy>
quint16 curvePoint::operator[](size_t index)
{
    index = OutOfBoundsPolicy(index, dimensions);
    return point[index];
}

Тогда вы можете определить несколько политик, которые может выбрать вызывающий. Например:

struct NoBoundsCheck {
    size_t operator()(size_t index, size_t /* max */) {
        return index;
    }
};

struct WrapAroundIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        return index % max;
    }
};

struct AssertIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        assert(index < max);
        return index % max;
    }
};

struct ThrowIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        if (index >= max) throw std::domain_error;
        return index;
    }
};

struct ClampIfOutOfBounds {
    size_t operator()(size_t index, size_t max) {
        if (index >= max) index = max - 1;
        return index;
    }
};
0 голосов
/ 22 июня 2009

Оператор по модулю работает на удивление хорошо для индексов массива - он также реализует отрицательные индексы (т. Е. point[-3] = point[dimensions - 3]). С ним легко работать, поэтому я лично рекомендую оператора по модулю, если он хорошо документирован.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...