Использование оператора [] на пустом std :: vector - PullRequest
8 голосов
/ 30 сентября 2010

Некоторое время назад мне советовали, что это обычное место для использования std :: vector в качестве безопасного динамического массива в исключениях в c ++ вместо выделения необработанных массивов ... например

{
    std::vector<char> scoped_array (size);
    char* pointer = &scoped_array[0];

    //do work

} // exception safe deallocation

Я использовал это соглашение несколько раз без проблем, однако недавно портировал некоторый код на Win32 VisualStudio2010 (ранее это было только в MacOS / Linux), и мои модульные тесты не работают (stdlibвыдает утверждение), когда размер вектора оказывается равным нулю .

Я понимаю, что запись в такой массив будет проблемой, но это предположение нарушает это решение как замену необработанным указателям.Рассмотрим следующие функции с n = 0

void foo (int n) {
   char* raw_array = new char[n];
   char* pointer = raw_array;
   file.read ( pointer , n );
   for (int i = 0; i < n; ++i) {
      //do something
   }
   delete[] raw_array;
}

Хотя приведенный выше код вполне допустим (я считаю), в то время как приведенный ниже код создаст утверждение для VisualStudio2010

void foo (int n) {
   std::vector<char> scoped_array (n);
   char* pointer = &scoped_array[0];
   file.read ( pointer , n );
   for (int i = 0; i < n; ++i) {
  //do something
   }
}

Я все время использовал неопределенное поведение?Я находился под впечатлением, что оператор [] не проверял ошибки, и это было допустимое использование std :: vector <>.Кто-нибудь еще сталкивался с этой проблемой?

- edit: Спасибо за все полезные ответы, в ответ людям, которые говорят, что это неопределенное поведение.Можно ли заменить вышеприведенное выделение необработанного массива, который будет работать с n = 0 ?

, при этом говоря, что проверка n = 0 в качестве исключительного случаярешить проблему (это будет).Существует много шаблонов, в которых не требуется особый случай (например, приведенный выше пример необработанного указателя), поэтому, возможно, потребуется использовать что-то отличное от std :: vector <>?

Ответы [ 7 ]

8 голосов
/ 30 сентября 2010

См. Выпуск LWG 464 . Это известная проблема. C ++ 0x (который частично реализован в MSVC 2010) решает эту проблему путем добавления члена .data().

4 голосов
/ 30 сентября 2010

Что касается стандарта C ++, то operator[] не гарантирует, что не проверяет, просто (в отличие от at()) проверка не гарантируется.

Вы ожидаете, что в реализации без проверки &scoped_array[scoped_array.size()] приведет к допустимому указателю в пределах или в один конец массива, выделенного вектором. Это явно не гарантировано, но для данной реализации вы можете проверить, посмотрев на ее источник. Для пустого вектора может вообще не быть выделения (как оптимизация), и я не вижу ничего в части vector стандарта, которая определяет результат scoped_array[0], кроме таблицы 68.

Исходя из таблицы 68, вы можете сказать, что результатом вашего выражения является &*(a.begin() + 0), что незаконно разыменовывает внешний итератор. Если векторный итератор вашей реализации является просто указателем, то вам, вероятно, это сойдет с рук - если нет, то, возможно, нет, и, очевидно, ваш - нет.

Я забыл результаты аргумента относительно того, является ли &* на указателе, который не должен быть разыменован, неактивным или нет. IIRC это не ясно из стандарта (некоторая двусмысленность где-то), что вызвало запросы на исправление стандарта, чтобы сделать его явно легальным. Это говорит о том, что он действительно работает на всех или на большинстве известных реализаций.

Лично я бы на это не полагался и не отключил бы проверку. Я бы переписал твой код:

char* pointer = (scoped_array.size() > 0) ? &scoped_array[0] : 0;

Или в этом случае просто:

char* pointer = (n > 0) ? &scoped_array[0] : 0;

Мне кажется неправильным использовать индекс n вектора, не зная, что его размер равен по крайней мере n + 1, независимо от того, действительно ли он работает в вашей реализации после того, как вы отключили проверку.

1 голос
/ 30 сентября 2010

operator [] возвращает ссылку, и поэтому вызов ее для пустого вектора должен быть неопределенным.

В конце концов, на какой элемент должна ссылаться ссылка, когда нет элементов?operator [] должен будет вернуть нулевую ссылку или полностью недействительную ссылку.И то и другое может привести к неопределенному поведению.

Так что да, вы все время использовали неопределенное поведение.Не обязательные, но все же совместимые проверки Visual Studio в operator [] только что выявили этот факт.

0 голосов
/ 30 сентября 2010

Не могли бы вы использовать итераторы вместо указателей?

{
    std::vector<char> scoped_array (size);
    std::vector<char>::iterator pointer = scoped_array.begin();

    //do work

} // exception safe deallocation
0 голосов
/ 30 сентября 2010

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

template<class InputIterator, class OutputIterator>
OutputIterator copy_n( InputIterator first, InputIterator last, OutputIterator result, std::size_t n)
{
    for ( std::size_t i = 0; i < n; i++ ) {
        if (first == last)
            break;
        else
            *result++ = *first++;
    }
    return result;
}

std::ifstream file("path_to_file");
std::vector<char> buffer(n);
copy_n(std::istream_iterator<char>(file), 
       std::istream_iterator<char>(),
       std::back_insert_iterator<vector<char> >(buffer),
       n);

Это скопирует содержимое файла в буфер n символов за раз.Когда вы перебираете буфер, используйте:

for (std::vector<char>::iterator it = buffer.begin(); it != buffer.end(); it++)

вместо счетчика.

0 голосов
/ 30 сентября 2010

Если вы хотите добиться более чистого поведения в этом сценарии, вы можете заменить использование a[0] на использование a.at(0), которое будет выбрасывать, если индекс недействителен.

Прагматичным решением будет инициализация векторас n + 1 записями и ограничивает доступ к 0..n-1 (как этот код уже делает).

void foo (int n) {
   std::vector<char> scoped_array (n+1);
   char* pointer = &scoped_array[0];
   file.read ( pointer , n );
   for (int i = 0; i < n; ++i) {
  //do something
   }
}
0 голосов
/ 30 сентября 2010

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

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