Я прихожу с Java и изучаю C ++ в данный момент. Я использую принципы построения и практики Страуструпа на C ++. Я сейчас работаю с векторами. На странице 117 он говорит, что доступ к несуществующему элементу вектора вызовет ошибку времени выполнения (то же самое в Java, индексирование вне границ). Я использую компилятор MinGW и когда я компилирую и запускаю этот код:
#include <iostream>
#include <cstdio>
#include <vector>
int main()
{
std::vector<int> v(6);
v[8] = 10;
std::cout << v[8];
return 0;
}
Это дает мне вывод 10. Еще более интересным является то, что если я не изменяю несуществующий векторный элемент (я просто печатаю его, ожидая ошибку времени выполнения или, по крайней мере, значение по умолчанию), он печатает несколько больших целых чисел. Итак … Страуструп не прав, или у GCC есть какие-то странные способы компиляции C ++?
Книга немного расплывчата. Это не столько «ошибка времени выполнения», сколько неопределенное поведение который проявляется во время выполнения. Это означает, что может произойти все, что угодно. Но ошибка строго с вы, не с выполнением программы, и фактически невозможно и даже неразумно говорить о выполнении программы с неопределенным поведением.
В C ++ нет ничего, что защищало бы вас от ошибок программирования, в отличие от Java.
Как говорит @sftrabbit, std::vector
имеет альтернативный интерфейс, .at()
, которая всегда дает правильную программу (хотя она может генерировать исключения) и, следовательно, ту, о которой можно рассуждать.
Позвольте мне повторить это на примере, потому что я считаю, что это важный фундаментальный аспект C ++. Предположим, мы читаем целое число от пользователя:
int read_int()
{
std::cout << "Please enter a number: ";
int n;
return (std::cin >> n) ? n : 18;
}
Теперь рассмотрим следующие три программы:
Опасный Правильность этой программы зависит от ввода пользователя! Это не обязательно некорректный, но это небезопасный (до точки, где я бы назвал это сломанным).
int main()
{
int n = read_int();
int k = read_int();
std::vector<int> v(n);
return v[k];
}
Безоговорочно правильно: Независимо от того, что вводит пользователь, мы знаем, как ведет себя эта программа.
int main() try
{
int n = read_int();
int k = read_int();
std::vector<int> v(n);
return v.at(k);
}
catch (...)
{
return 0;
}
Нормальный: Выше версия с .at()
неловко Лучше проверить и оставить отзыв. Поскольку мы выполняем динамическую проверку, неконтролируемый векторный доступ гарантированно будет работать нормально.
int main()
{
int n = read_int();
if (n <= 0) { std::cout << "Bad container size!\n"; return 0; }
int k = read_int();
if (k < 0 || k >= n) { std::cout << "Bad index!\n"; return 0; }
std::vector<int> v(n);
return v[k];
}
(Мы игнорируем возможность того, что конструкция вектора может вызвать собственное исключение.)
Мораль состоит в том, что многие операции в C ++ небезопасны и только условно корректны, но от программиста ожидается, что вы сделаете необходимые проверки заранее. Язык не делает это для вас, и поэтому вы не платите за это, но вы должны помнить, чтобы сделать это. Идея заключается в том, что в любом случае вам нужно обрабатывать условия ошибки, и поэтому вместо принудительного выполнения дорогостоящей, неконкретной операции на уровне библиотеки или языка ответственность остается за программистом, который лучше может интегрировать проверку в код, который должен быть написан в любом случае.
Если бы я хотел быть шутливым, я бы сравнил этот подход с Python, который позволяет писать невероятно короткая а также правильный программы, без какой-либо написанной пользователем обработки ошибок вообще. Обратная сторона в том, что любая попытка использовать такую программу, которая отклоняется только немного из того, что задумал программист, у вас останется неконкретное, трудно читаемое исключение и трассировка стека и небольшое руководство о том, что вы должны были сделать лучше. Вы не обязаны писать какую-либо обработку ошибок, и часто никакая обработка ошибок не заканчивается записью. (Я не могу полностью противопоставить C ++ Java безопасный, Я еще не видел короткая Java программа.)</ rantmode>
C и C ++ не всегда делают проверки границ. Это МОЖЕТ вызвать ошибку во время выполнения. И если вы переусердствуете со своим числом, скажем, 10000 или около того, это почти наверняка вызовет проблему.
Вы также можете использовать vector.at (10), что определенно должно дать вам исключение.
увидеть:
http://www.cplusplus.com/reference/vector/vector/at/
по сравнению с:
http://www.cplusplus.com/reference/vector/vector/operator%5B%5D/
Я надеялся, что vector «operator []» проверит границу как «at ()», потому что я не так осторожен. 🙂
Один из способов — унаследовать векторный класс и переопределить оператор [] для вызова at (), чтобы можно было использовать более читабельное «[]» и не нужно заменять все «[]» на «at ()». Вы также можете определить унаследованный вектор (например, safer_vector) как нормальный вектор.
Код будет таким (в C ++ 11, llvm3.5 из Xcode 5).
#include <vector>
using namespace std;
template <class _Tp, class _Allocator = allocator<_Tp> >
class safer_vector:public vector<_Tp, _Allocator>{
private:
typedef __vector_base<_Tp, _Allocator> __base;
public:
typedef _Tp value_type;
typedef _Allocator allocator_type;
typedef typename __base::reference reference;
typedef typename __base::const_reference const_reference;
typedef typename __base::size_type size_type;
public:
reference operator[](size_type __n){
return this->at(__n);
};
safer_vector(_Tp val):vector<_Tp, _Allocator>(val){;};
safer_vector(_Tp val, const_reference __x):vector<_Tp, _Allocator>(val,__x){;};
safer_vector(initializer_list<value_type> __il):vector<_Tp, _Allocator>(__il){;}
template <class _Iterator>
safer_vector(_Iterator __first, _Iterator __last):vector<_Tp,_Allocator>(__first, __last){;};
// If C++11 Constructor inheritence is supported
// using vector<_Tp, _Allocator>::vector;
};
#define safer_vector vector
Это ценный комментарий @Evgeny Sergeev, который я предлагаю в ответ:
Для GCC вы можете -D_GLIBCXX_DEBUG заменить стандартные контейнеры на безопасные реализации. Совсем недавно, похоже, теперь это работает с std :: array. Более подробная информация здесь: gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode.html
Я бы добавил, что также возможно связывать отдельные «безопасные» версии векторных и других служебных классов, используя префикс gnu_debug :: namespace вместо std ::.
Другими словами, не изобретайте колесо, проверки массива доступны по крайней мере с GCC.