#c
#c
Вопрос:
У меня часто возникает ошибка выхода индекса за пределы, которую можно легко обнаружить, если я использую .at()
вместо []
для доступа к элементу в векторе или строке. Однако at()
замедляет мою программу в 5 раз из-за проверки границ.
Я пытаюсь написать макрос для замены .at(someVariable)
на [someVariable]
таким образом, я могу просто раскомментировать макрос вместо того, чтобы заменять каждый .at()
на []
вручную. Я прочитал документацию по макросам на cppreference.com но, похоже, не могу придумать способ получить эту функциональность.
Ответ №1:
В общем, я бы избегал такого рода макросов, поскольку они «невидимы» (поэтому измененная семантика незаметно скрыта от тех, кто не знает) и может изменять функциональность кода, который, как ожидается, at
выйдет за пределы, даже в сборках релизов. Во всяком случае, я бы определил что-то, что выделяется, например, все в верхнем регистре AT
.
К счастью, на самом деле это не требуется, поскольку среды выполнения C «большой тройки» уже имеют встроенную функциональность для условного включения проверки границ operator[]
(что также имеет то преимущество, что оно более читабельно, чем at
):
- если вы используете g / libstdc , то при передаче
-D_GLIBCXX_DEBUG
вы получите отладочную версию контейнеров STL, которые выполняют проверки границoperator[]
, плюс множество других проверок отладки на итераторах; - CLang / libc может выполнять аналогичные проверки, устанавливая
_LIBCPP_DEBUG
значение 1; - для Visual C аналогичная функциональность включена с
ITERATOR_DEBUG_LEVEL
значением2
(и она уже включена по умолчанию в отладочных сборках).
Кстати, некоторые из этих ошибок (те, которые фактически превышают выделенный размер, а не только «логически допустимый» размер вектора) также могут быть обнаружены с помощью средства очистки адресов ( -fsanitize=address
в gcc и clang) или valgrind (медленно!) и подобных инструментов.
Ответ №2:
Вот один из способов, который не включает макросы — он использует c 17 if constexpr
для простоты:
#include <vector>
constexpr bool safe_mode = true; // or false
template<class Container>
auto at(Containeramp; v, std::size_t i) -> decltype(auto)
{
if constexpr (safe_mode)
return v.at(i);
else
return v[i];
}
intamp; test(std::vector<int>amp; v)
{
return at(v, 6);
}
Комментарии:
1. Почему
decltype(auto)
тянется?2. @CruzJean если вы имеете в виду, почему я решил написать это именно так, вероятно, в силу привычки. Я пишу достаточное количество шаблонного кода и считаю полезным иметь в своем распоряжении все типы функций / методов. Поэтому я склонен стандартизировать конечные возвращаемые типы. Сказав это, я вижу, что я смешал стили в приведенном выше коде. 🙂
3. Ах, мне просто было любопытно, было ли какое-то тонкое различие, о котором я не знал.
4. По состоянию на год назад, по предложению одного из руководителей проекта в нашем приложении, я начал использовать конечные типы возвращаемых данных преимущественно в своем коде (почти исключительно), и я нахожу их гораздо более удобочитаемыми. Это и в сочетании с «почти всегда автоматически», результирующий код C разбирается намного чище. Все зависит от мнения, я знаю.
Ответ №3:
Вы могли бы использовать полное имя оператора разыменования массива:
#define at(x) operator[](x)
(x)
Часть не является существенной, но заменит только at
, если за ней следует аргумент, а не заменяет слово само по себе. Вы также захотите определить это после включения всех стандартных заголовков, иначе это заменит at
объявления функций-членов в классах, которые имеют это, что приведет к ошибкам компиляции.
Комментарии:
1. Почему вам не нужен . before at в макросе? Не должен ли он заменить . тоже?
2. @user3586940 Вы не можете заменить
.
в макросе, так как.
не может быть в имени макроса.