C ++ STL: проблемы с итераторами строк

Я делаю простую игру с палачом из командной строки.

void Hangman::printStatus()
{
    cout << "Lives remaining: " << livesRemaining << endl;
    cout << getFormattedAnswer() << endl;
}

string Hangman::getFormattedAnswer()
{
    return getFormattedAnswerFrom(correctAnswer.begin(), correctAnswer.end());
}

string Hangman::getFormattedAnswerFrom(string::const_iterator begin, string::const_iterator end)
{
    return begin == end? "" : displayChar(*begin) + getFormattedAnswerFrom(++begin, end);
}

char Hangman::displayChar(const char c)
{
    return c;
}

(В конце концов, я изменю это так, чтобы displayChar() отображал - или символ, если пользователь догадался об этом, но для простоты теперь я просто возвращаю все.)

Когда я собираю и запускаю это из VS 2010, я получаю всплывающее окно:

Ошибка отладки!

Строка xstring: 78

Выражение: строковый итератор не разыменовывается

Что я делаю неправильно?


person Nick Heiner    schedule 27.05.2010    source источник
comment
Не могли бы вы попробовать получить обратную трассировку во время отладки, чтобы убедиться, что опубликованный вами код вызывает такое поведение?   -  person jpalecek    schedule 28.05.2010
comment
Есть ли конкретная причина, по которой вы используете рекурсивную реализацию? Итеративная реализация была бы проще.   -  person James McNellis    schedule 28.05.2010
comment
@ Джеймс, да, ты прав. Я реализовал это итеративно, и теперь он работает.   -  person Nick Heiner    schedule 28.05.2010
comment
Похоже, что проблема возникает постепенно, когда begin == end - 1   -  person Justin Ardini    schedule 28.05.2010


Ответы (3)


Проблема заключается в оценке:

displayChar(*begin) + getFormattedAnswerFrom(++begin, end)

При выполнении этого оператора очевидно, что ваш компилятор сначала увеличивает begin, возвращает «следующий» begin для использования в качестве первого аргумента для getFormattedAnswerFrom и затем разыменовывает begin для аргумента для displayChar.

Когда begin на единицу отстает от end, тогда begin != end, так что displayChar(*begin) + getFormattedAnswerFrom(++begin, end) будет работать. Ваш компилятор увеличивает begin, поэтому теперь begin == end, и разыменование begin недействительно.

См. Также: Порядок оценки в параметрах функций C ++

person Daniel Trebbien    schedule 27.05.2010
comment
Определяет ли спецификация C ++ порядок оценки подвыражений бинарных операторов или это обычное дело, чтобы идти справа налево, как ваше утверждение? - person David Gladfelter; 28.05.2010
comment
@David: Это UB, по крайней мере, для примитивных типов. Для типов пользователей он не определен, но тоже может быть неопределенным (я точно не знаю). - person jpalecek; 28.05.2010
comment
@David, я не думаю, что стандарт C ++ определяет, будет ли сначала выполняться *begin или ++begin. Я не собирался утверждать оценку подвыражений справа налево, поэтому обновил свой ответ. - person Daniel Trebbien; 28.05.2010
comment
Фактически, я когда-то работал в компании, которая использовала компиляторы Intel и Visual C ++. Компилятор Intel обычно оценивал слева направо, а компилятор Visual C ++ - справа налево. Это привело к нескольким проблемам, когда некоторые тестовые примеры были успешными, если были скомпилированы с icc, но потерпели неудачу, если были скомпилированы с cl. - person Daniel Trebbien; 28.05.2010
comment
Я думал, что порядок операций идет слева направо? В любом случае, это еще один пример того, почему следует избегать ++ x или x ++, если они используются как часть большего выражения. - person Puppy; 28.05.2010

Если correctAnswer пусто, correctAnswer.begin() будет таким же, как correctAnswer.end() и не будет разыменован.

person fbrereto    schedule 27.05.2010
comment
Но не следует ли тогда немедленно прекратить рекурсию? - person jpalecek; 28.05.2010
comment
У него есть чек на это. Вы говорите, что даже если begin == end, displayChar (* begin) + getFormattedAnswerFrom (++ begin, end); все равно будет вычисляться? - person Anthony; 28.05.2010
comment
@jpalecek: Да, это правда. - person fbrereto; 28.05.2010

Мне это кажется нормальным. Однако помните, что любое повреждение кучи или стека может вызвать эту ошибку. Вам нужно получить трассировку стека и заглянуть внутрь rightAnswer и убедиться, что и он, и рассматриваемый экземпляр Hangman являются допустимыми объектами.

Кроме того, я просто немного обеспокоен вашими функциями здесь. Они кажутся очень странными. Почему бы просто не заменить на std :: for_each?

Изменить @ комментарий:
Если у вас есть C ++ 0x, вы можете просто сделать

std::for_each(correctAnswer.begin(), correctAnswer.end(), [this](const char& ref) {
    std::cout << this->displayChar(ref);
});

В противном случае вам нужно будет сделать что-то вроде этого:

struct helper {
    Hangman* ptr;
    void operator()(const char& ref) {
        std::cout << ptr->displayChar(ref);
    }
};
helper Helper;
Helper.ptr = this;
std::for_each(correctAnswer.begin(), correctAnswer.end(), Helper);
person Puppy    schedule 27.05.2010
comment
Отредактировано. У вас не должно быть проблем с этим. - person Puppy; 28.05.2010
comment
Интригующе. Не могли бы вы объяснить синтаксис верхнего примера? Какое значение имеет [this]? - person Nick Heiner; 28.05.2010
comment
При использовании лямбда-захвата, [this] - это специальное разрешение, при котором указатель this захватывается по значению, а результирующая лямбда фактически считается лямбда-членом. msdn.microsoft.com/en-us/library/ Строго говоря, мне не нужно было разыменовать это внутри тела, но я все равно сделал это. - person Puppy; 28.05.2010