Хотя на C++ нет прямой поддержки операторов switch со строками, эффективные способы реализовать строковые переключения имеются. Изучим эти подходы на практических примерах и реальных сценариях.

Метод 1. Хеширование строк

Наиболее эффективный подход  —  преобразование строк в уникальные целые числа посредством хеш-функции:

#include <string>
#include <functional>

enum class StringCode {
hello,
quit,
help,
unknown
};

StringCode hashString(const std::string& str) {
if (str == "hello") return StringCode::hello;
if (str == "quit") return StringCode::quit;
if (str == "help") return StringCode::help;
return StringCode::unknown;
}

void processCommand(const std::string& command) {
switch (hashString(command)) {
case StringCode::hello:
std::cout << "Hello there!" << std::endl;
break;
case StringCode::quit:
std::cout << "Goodbye!" << std::endl;
break;
case StringCode::help:
std::cout << "Available commands: hello, quit, help" << std::endl;
break;
default:
std::cout << "Unknown command" << std::endl;
}
}

Этот быстрый метод с экономным расходом памяти идеален для работы с интерфейсами командной строки или параметрами конфигурации.

Метод 2. std::unordered_map

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

#include <iostream>
#include <string>
#include <unordered_map>
#include <functional>

class CommandProcessor {
std::unordered_map<std::string, std::function<void()>> commands;

public:
CommandProcessor() {
commands["help"] = []() {
std::cout << "Available commands: help, add, remove" << std::endl;
};
commands["add"] = []() {
std::cout << "Adding item..." << std::endl;
};
commands["remove"] = []() {
std::cout << "Removing item..." << std::endl;
};
}

void execute(const std::string& command) {
auto it = commands.find(command);
if (it != commands.end()) {
it->second();
} else {
std::cout << "Unknown command: " << command << std::endl;
}
}
};

int main() {
CommandProcessor processor;
processor.execute("help"); // Показываются доступные команд
processor.execute("add"); // Добавляется элемент
processor.execute("remove"); // Удаляется элемент
processor.execute("invalid"); // Показывается ошибка
return 0;
}

При таком более сопровождаемом подходе команды изменяются во время выполнения.

Метод 3. Переключение строк с Constexpr

В версии C++17 и новее строки переключаются во время компиляции при помощи constexpr:

#include <string_view>
#include <iostream>

constexpr uint64_t hash(std::string_view str) {
uint64_t hash = 0;
for (char c : str) {
hash = (hash * 131) + c;
}
return hash;
}

constexpr uint64_t operator"" _hash(const char* str, size_t len) {
return hash(std::string_view(str, len));
}

void processAction(std::string_view action) {
switch (hash(action)) {
case "save"_hash:
std::cout << "Saving data..." << std::endl;
break;
case "load"_hash:
std::cout << "Loading data..." << std::endl;
break;
case "delete"_hash:
std::cout << "Deleting data..." << std::endl;
break;
default:
std::cout << "Unknown action: " << action << std::endl;
}
}

Этим методом обеспечивается хеширование строк во время компиляции с производительностью времени выполнения, аналогичной целочисленным переключениям.

Реальный сценарий: синтаксический анализатор команд

Вот практический пример парсера команд для текстового редактора:

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

class TextEditor {
private:
std::vector<std::string> buffer;
size_t currentLine = 0;

enum class CommandType {
insert,
delete_line,
move,
print,
unknown
};

CommandType getCommandType(const std::string& cmd) {
if (cmd == "insert" || cmd == "i") return CommandType::insert;
if (cmd == "delete" || cmd == "d") return CommandType::delete_line;
if (cmd == "move" || cmd == "m") return CommandType::move;
if (cmd == "print" || cmd == "p") return CommandType::print;
return CommandType::unknown;
}

public:
void executeCommand(const std::string& commandLine) {
std::istringstream iss(commandLine);
std::string cmd;
iss >> cmd;

switch (getCommandType(cmd)) {
case CommandType::insert: {
std::string text;
std::getline(iss >> std::ws, text);
buffer.insert(buffer.begin() + currentLine, text);
currentLine++;
break;
}
case CommandType::delete_line: {
if (!buffer.empty() && currentLine < buffer.size()) {
buffer.erase(buffer.begin() + currentLine);
}
break;
}
case CommandType::move: {
size_t line;
if (iss >> line && line < buffer.size()) {
currentLine = line;
}
break;
}
case CommandType::print: {
for (size_t i = 0; i < buffer.size(); i++) {
std::cout << i << ": " << buffer[i] << std::endl;
}
break;
}
default:
std::cout << "Unknown command: " << cmd << std::endl;
std::cout << "Available commands: insert/i, delete/d, move/m, print/p"
<< std::endl;
}
}
};

int main() {
TextEditor editor;

// Пример использования
editor.executeCommand("insert First line");
editor.executeCommand("i Second line");
editor.executeCommand("print");
editor.executeCommand("move 0");
editor.executeCommand("delete");
editor.executeCommand("p");

return 0;
}

Оптимизация производительности: объединение строк в пул

Там, где строк много, производительность повышается их объединением в пул:

#include <iostream>
#include <string>
#include <unordered_set>
#include <memory>

class StringPool {
private:
std::unordered_set<std::string> pool;

public:
const std::string* intern(const std::string& str) {
auto [it, inserted] = pool.insert(str);
return &(*it);
}
};

class CommandProcessor {
private:
StringPool stringPool;

void processPooledString(const std::string* str) {
if (str == stringPool.intern("start")) {
std::cout << "Starting process..." << std::endl;
} else if (str == stringPool.intern("stop")) {
std::cout << "Stopping process..." << std::endl;
} else if (str == stringPool.intern("pause")) {
std::cout << "Pausing process..." << std::endl;
} else {
std::cout << "Unknown command: " << *str << std::endl;
}
}

public:
void processCommand(const std::string& command) {
const std::string* pooledString = stringPool.intern(command);
processPooledString(pooledString);
}
};

При таком подходе сравниваются не строки, а указатели. В итоге производительность на часто используемых строках повышается значительно.

Заключение

Хотя прямой поддержки строк в операторах switch на C++ нет, эффективные альтернативы имеются:

  1. Хеширование строк для простых наборов статических строк.
  2. std::unordered_map для гибкой, динамической обработки команд.
  3. Хеширование строк при помощи Constexpr для оптимизации во время компиляции.
  4. Объединение строк в пул для высокопроизводительных приложений.

Выбирайте метод, оптимальный для конкретных задач:

  • хеширование строк  —  для небольших фиксированных наборов строк;
  • std::unordered_map  —  для динамических систем команд;
  • хеширование при помощи constexpr  —  когда важна проверка во время компиляции;
  • объединение строк в пул  —  там, где важна производительность.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи ryan: Using Switch Statements with Strings in C++: A Complete Guide

Предыдущая статья10 полезных приемов работы со строками JavaScript
Следующая статьяПайплайн рендеринга во Flutter: фаза сборки