Как использовать 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. Это накладные расходы, потому что вам нужно сделать кучуstrlens:

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{};
    }
};
Другие вопросы по тегам