эффективный способ передачи списка строк - PullRequest
0 голосов
/ 19 февраля 2020
#include <iostream>
#include <vector>
#include <string>

void function1(const char** keywords) {  // C like
    for (const char** keyword = keywords; *keyword; ++keyword) {
        std::cout << *keyword << std::endl;
    }
}

void function2(const std::vector<std::string>& keywords) {  // C++11 like
    for(const std::string& keyword : keywords) {
        std::cout << keyword << std::endl;
    }
}

int main() {
    static const char* keywords[] = { "hello", "world", "!", 0 };
    function1(keywords);
    function2({ "hello", "world", "!" });
    return 0;
}

Обе функции дают одинаковый результат. Мне нравится новый формат инициализации c ++ 11, где значения встроены, и я могу пропустить пустое значение (ноль). Проблема в производительности. Я ожидаю, что function2 будет динамически выделять память при каждом вызове. Я полагаю, это не будет эффективным. Есть ли лучший способ реализовать такую ​​функцию с помощью метода c ++, сохраняя старый C efficency?

Обновление:

Есть небольшое недоразумение. Меня беспокоит использование std :: vector, которое вызывает динамическое выделение памяти c. На самом деле я бы использовал std :: string в теле функции function1, поэтому std :: string не имеет значения. Тот же std :: cout был добавлен мной в качестве примера использования (не реальный случай). std :: array не может использоваться здесь, потому что длина списка не фиксирована.

Я расширяю свой вопрос: цель состоит в том, чтобы использовать только один аргумент с пустым значением по умолчанию для передачи списка строк. Как добиться этого наиболее эффективным и красивым способом?

Так что вышеупомянутые объявления функций могут выглядеть следующим образом:

void function1(const char** keywords = nullptr)

void function2(const std::vector<std::string>& keywords = {})

Здесь я вижу следующую проблему. Вызов функции 1 без параметра ничего не стоит, но я ожидаю инициализации пустого вектора в функции 2. Я прав?

Ответы [ 4 ]

0 голосов
/ 21 февраля 2020

Спасибо за все ваши ответы. На основе ответа @ Jarod42 я реализовал свой аналогичный.

template <typename StringContainer = std::initializer_list<const char*>>
void function(const StringContainer& keywords = {}) {
    if (!keywords.size()) {
        return;
    }
    for (const auto& keyword : keywords) {
        std::cout << keyword << std::endl;
    }
}

int main() {
    function();
    function({ "hello", "world", "!" });
    std::vector<std::string> keys = { "hello", "world", "!" };
    function(keys);
    return 0; 
}

Буду признателен за все комментарии.

0 голосов
/ 19 февраля 2020

Если есть выбор для C ++ 17, есть новый класс std :: string_view , который именно то, что вы хотите.

void function2(const std::vector<std::string_view>& keywords) {  // C++17 
    for(auto const& keyword : keywords) {
        std::cout << keyword << std::endl;
    }
}
0 голосов
/ 19 февраля 2020

Существует 2 трудности: const char* против std::string (или даже std::string_view в C ++ 17)

и коллекция указанного выше типа.

как Container<const char*>, Container<std::string> и Container<std::string_view> - это разные типы, которые требуют преобразования для перехода от одного к другому.

Чтобы избежать этих трудностей, вы можете использовать шаблон:

template <typename StringContainer>
void function3(const StringContainer& keywords) {
    for(const auto& keyword : keywords) {
        std::cout << keyword << std::endl;
    }
}

, но вы затем нельзя использовать

function3({ "hello", "world", "!" });

, поскольку {/*..*/} не имеет типа. Он может соответствовать шаблону C -array или initializer_list.

Таким образом, вы можете добавить дополнительную перегрузку для обработки этого случая:

template <typename String>
void function3(std::initializer_list<String> keywords) {
    return function3<std::initializer_list<String>>(keywords);
}

и для обработки пустых initializer_list, или аргумент по умолчанию, вы можете сделать:

template <typename String = std::string>
void function3(std::initializer_list<String> keywords = {}) {
    return function3<std::initializer_list<String>>(keywords);
}

Демо

0 голосов
/ 19 февраля 2020

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

Даже если функция не встроена в функцию, вектор может быть подготовлен во время компиляции, что значительно экономит конструкции котельной плиты.

Но вы попадаете в темную часть, когда хотите использовать код в более чем одной системе: вы не можете быть уверены, что конкретный c компилятор переведет его.

Таким образом, вы попадаете на обычное правило для низкоуровневой оптимизации: оно не имеет смысла с точки зрения языка, а только для конкретной реализации c с определенной конфигурацией c (параметры компиляции и компоновки). И тогда единственным надежным способом является чтение ассемблерного кода и тестирование производительности для различных версий кода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...