2010-09-30 3 views
7

Мне было сообщено некоторое время назад, что было обычным явлением использовать std :: vector как исключающий безопасный динамический массив в C++ вместо выделения необработанных массивов ... напримерИспользование оператора [] на пустом std :: vector

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

    //do work 

} // exception safe deallocation 

Я использовал настоящую Конвенцию в несколько раз без каких-либо проблем, однако я недавно портирован код для Win32 VisualStudio2010 (ранее это было только на MacOS/Linux) и мои модульных тестов ломаются (STDLIB бросает assert), когда размер вектора равен нулю.

Я понимаю, что запись в такой массив будет проблемой, но это предположение нарушает это решение как замену исходным указателям. Рассмотрим следующие функции с п = 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 <>?

+1

Какое исключение выбрасывается? Я не вижу ничего плохого в коде. – fschmitt

+2

@fschmitt: Если вектор пуст, то 'scoped_array [0]' дает неопределенное поведение. В этом случае, создавая вариант отладки с Visual C++, он не выполняет проверку диапазона и выдает исключение. –

+0

@fschmitt: визуальная студийная реализация std :: vector запускает assert (т. Е. Убивает весь процесс немедленно) с ошибкой, говорящей, что на векторе была ошибка за пределами границ. – Akusete

ответ

8

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

+0

.data() был бы идеальным, слишком плохим, его единственным в C++ 0x – Akusete

+0

Я принимаю этот ответ просто потому, что он ссылается на дискуссию о том, почему этот тип операции требуется, и что будет (в конечном итоге) стандартным способ сделать это. – Akusete

0

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

+0

Это не стандарт, но очень полезно быстро отладить незавершенный код.К сожалению, это привело к необходимости настройки путаницы макросов для получения желаемого поведения - я не знаю, является ли это чище в более поздних версиях (VC9/VC10). –

+0

@Steve: Вы имеете в виду, что стандарт * forbids * checked 'operator []' making' & vec [0] 'всегда действителен? У вас есть ссылка, где написано? Редактировать: неважно, Кубби ответил на этот вопрос. – ybungalobill

0

Если вы хотите получить более чистое поведение в этом сценарии, вы можете заменить использование 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

Это противоположность тому, что он на самом деле хочет. Он хочет, чтобы он не метался, точно так же, как код массива/указателя не бросает. –

+0

@Steve Jessop - Я прочитал цель как «безопасный для исключения динамический массив в C++». Код в его нынешнем виде будет утверждать (в лучшем случае) и вину (более типично). –

+0

Что касается кода примера, я думаю, что ключевым словом является * array *. Массив предоставляет указатель «один за прошлым», который вы можете законным образом переходить к 'read', если количество прочитанных байтов равно 0. Вектор не дает вам этого указателя, просто одно-прошлое -end * iterator *. Таким образом, в этом отношении не является безопасным для исключения динамическим массивом. Я думаю, что векторный код Akusete будет либо утверждать, либо обычно работать. Я не знаю, как выглядит реализация, на которой она действительно будет виновата, хотя мы уверены, что это разрешено. –

0

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

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++) 

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

4

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

Вы ожидаете, что в неконтролирующей реализации &scoped_array[scoped_array.size()] приведет к легальному указателю либо внутри, либо по одному в конце массива, выделенного вектором. Это явно не гарантировано, но для конкретной реализации вы можете проверить, просмотрев ее источник. Для пустого вектора вообще не может быть выделение (в качестве оптимизации), и я не вижу ничего в части стандарта, которая определяет результат 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; 

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

+0

Согласен, лучше переписать этот код, чем пытаться обойти чек, это не так. –

1

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

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

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

0

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

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

    //do work 

} // exception safe deallocation 
+0

В общем, нет, я искал способ выделить обычные указательные массивы безопасным способом (вроде auto_ptr для массива). У меня сложилось впечатление, что хорошей практикой является использование std :: vector для такой цели. – Akusete

Смежные вопросы