Как использовать span для завершения аргументов командной строки
Это использование грядущего C++20 std::span
правильно и без накладных расходов на завершение аргументов командной строки?
#include <iostream>
#include <span>
int main(int argc, const char* argv[])
{
for (auto s : std::span { argv, static_cast<std::size_t>(argc) })
std::cout << s << std::endl;
}
Если это правильно, могу я пойти дальше и использовать с std::string_view
?
2 ответа
Если вы используете ()
вместо того {}
вам не нужно действительно подробное приведение:
std::span(argv, argc)
Это дает вам ряд char const*
. Вы можете преобразовать их вstring_view
с помощью transform
. Это накладные расходы, потому что вам нужно сделать кучуstrlen
s:
std::span(argv, argc)
| std::views::transform([](char const* v){ return std::string_view(v); })
Для подобных вещей у меня есть объект-функция, который выполняет приведение, что довольно легко написать, так что это может быть:
std::span(argv, argc)
| std::views::transform(static_cast_<std::string_view>)
Раньше я сталкивался с такой ситуацией, когда мне действительно хотелось хорошо поиграть со стандартной библиотекой и использовать std::string_view вместо const char *, и я действительно не хотел ничего выделять для начала. Просто потому, что думать об этом не было причин.
Самое неловкое, конечно, то, что нам предоставляется переменное количество аргументов, а аргументы не указывают размер. Нам нужен способ: а) знать размер каждого аргумента или б) уже иметь место для его вычисления один раз. Таким образом, мы застряли в том, что кажется неопределенностью, потому что нам, вероятно, придется выделить память для обработки b) (если только мы не знаем ограничение на количество аргументов, которые должна обрабатывать наша программа) или каким-то волшебным образом узнать, каков размер аргумент есть.
Итак, вот пример моего грязного трюка:
for (size_t i = 0; (i+1) < argc; i++) {
std::cout << std::string_view{argv[i], argv[i + 1] - 1} << '\n';
}
Почему это работает? Ну, подпись не обязательно скажет вам об этом, но есть большая вероятность, что метод обработки передачи параметров командной строки между программами в любой операционной системе будет предельно простым (по соображениям производительности и т. д.). Как чертовски просто? Ну, предоставленный список argv[] обычно представляет собой массив, указывающий на непрерывный блок памяти. Это связано с тем, что вы можете анализировать аргументы командной строки в терминале за постоянное время и с известными ограничениями пространства (поскольку вам нужно создать буфер, достаточно длинный, чтобы обрабатывать все, что вы набрали), поэтому поместить результаты в непрерывную память тривиально. блок, который вмещает хотя бы столько же, а затем проделайте небольшую работу. Преимущество заключается в том, что передача одних и тех же аргументов или модификаций командной строки может быть столь же простой, как добавление или усечение этого блока. Таким образом, реализация имеет тенденцию заключаться в том, что буфер из терминала сшивается вместе, а затем соответствующим образом разделяется на части с помощью '\0', где начало каждого нового аргумента командной строки сохраняется в списке. Итак, если мы учтем тот факт, что каждый указатель будет строкой в стиле C, мы можем создать string_views, отрезав завершающий '\0'.
Исключением является argv[argc-1]!, поскольку argv[argc], скорее всего, будет нулевым или просто недействительным. Итак, мы можем написать что-то вроде этого:
__forceinline std::string_view argument(int argc, const char* argv[], int i) {
return (i+1) < argc ?
std::string_view{argv[i],argv[i+1]-1}:
std::string_view{argv[i]}
}
Итак, как ни странно, мы знаем размер каждого аргумента, кроме одного... последнего, но это идеально, потому что это постоянное ограничение пространства. Итак, другой подход заключается в том, что мы всегда можем зарезервировать место, чтобы узнать размер хотя бы одного аргумента (одна переменная size_t, находящаяся где-то поблизости) и использовать его вместо результата argv[argc-1].
Теперь единственное использование функции, подобной strlen, — это обработка последнего параметра командной строки. Мы можем дополнительно защититься от доступа к аргументам после argc, если захотим.
Обратной стороной является то, что если кто-то другой использует и вызывает вашу программу напрямую, и у него нет такого же расположения памяти для своих аргументов, тогда ваша программа выйдет из строя. Однако можно довольно быстро проверить работоспособность, что вы имеете дело с непрерывным блоком памяти, выполнив strlen для каждого аргумента и проверив, что длина каждой c-строки соответствует argv[i+1]-1.
Мы по-прежнему можем использовать приведенную выше функцию, если захотим, за исключением того, что теперь мы можем уменьшить argc, чтобы вызвать std::string_view{argv[i]}, который вместо этого будет использовать конструктор strlen для любых параметров, которые, как мы знаем, не являются частью этот начальный непрерывный блок памяти. Тогда использование будет примерно таким:
std::string_view argument_i = i < argc ? argument(known_contiguous, argc, i) : std::string_view{};
Затраты на безопасное выполнение этого действия по существу соответствуют стоимости создания вектора, за исключением распределения. Я предполагаю, что мой трюк здесь, вероятно, можно преобразовать для работы с диапазонным подходом.
Вот базовый класс, который справится с этим.
#pragma once
#include <string_view>
struct arguments {
private:
int argc = {};
const char** argv = {};
int contiguous_argc = {};
size_t last_argument_size = {};
public:
arguments(int arg_count, const char* arg_values[]) {
argc = arg_count;
argv = arg_values;
for (int i = 0; i < argc; i++) {
last_argument_size = strlen(argv[i]);
if (argv[i + 1] == 0)
break;
size_t constant_length = (argv[i + 1] - argv[i]) - 1;
if (last_argument_size != constant_length)
break;
contiguous_argc += 1;
}
}
std::string_view argument(int i) {
size_t constant_length = (argv[i + 1] - argv[i]) - 1;
return
(i <= contiguous_argc) ? std::string_view{argv[i], (i == contiguous_argc) ? last_argument_size : constant_length} :
(i < argc) ? std::string_view{argv[i]} : std::string_view{};
}
};