В тази публикация проучихме използването на std::visit
в C++, което е мощна помощна програма, която ни позволява да прилагаме различни функции към вариантен обект въз основа на текущия му тип. std::visit
може да бъде полезен в сценарии, при които трябва да обработваме вариантни обекти с множество възможни типове, като например в парсер, където може да се наложи да обработваме различни типове входни данни по различен начин. За да разберем какво постига std::visit
, разгледахме алтернативен начин за постигане на същата функционалност, използвайки оператори if-else и претоварване на функции.
Прост пример
Ето прост типичен пример за итерация през масив от варианти на 3 различни типа и извикващи функции, претоварени за всеки тип:
#include <iostream> #include <variant> #include <vector> void func(int i) { std::cout << "Called func(int): " << i << std::endl; } void func(double d) { std::cout << "Called func(double): " << d << std::endl; } void func(const std::string& s) { std::cout << "Called func(string): " << s << std::endl; } int main() { std::vector<std::variant<int, double, std::string>> myVector = {1, 3.14, "Hello"}; for (auto& element : myVector) { std::visit([](auto&& arg){ func(arg); }, element); } return 0; }
В този пример дефинираме три претоварени функции: func(int)
, func(double)
и func(const std::string&)
. След това създаваме вектор от std::variant<int, double, std::string>
и го инициализираме с три елемента от различни типове.
В цикъла for ние използваме std::visit
, за да извикаме подходящото претоварване на функцията за всеки елемент във вектора. std::visit
приема извикваем обект като свой първи аргумент, който в този случай е ламбда функция, която извиква func
със своя аргумент. Синтаксисът auto&&
във функцията ламбда казва на компилатора да изведе типа на аргумента и да създаде препратка към него, което ни позволява да извикаме func
с правилното претоварване за всеки тип вариант.
std::visit
е полиморфна функция по време на изпълнение, която определя типа на вариантния обект по време на изпълнение и след това извиква претоварване на съответната функция.
Когато извикаме std::visit
, ние му предаваме извикваем обект, който дефинира набор от претоварени функционални обекти за всеки тип във варианта. По време на изпълнение std::visit
използва типа на вариантния обект, за да определи кой функционален обект да извика, и след това предава вариантния обект на този функционален обект.
Така че в примера, който предоставих, ламбда функцията, предадена на std::visit
, е обект на посетител, който дефинира набора от претоварени func
функции за всеки тип във варианта. По време на изпълнение std::visit
определя типа на вариантния обект във вектора и след това извиква подходящата func
функция за този тип.
Как std::visit
знае типа на вариантния обект?
std::visit
използва информацията за типа, съхранена в самия вариантен обект, за да определи неговия тип по време на изпълнение. Когато създаваме вариантен обект, той съдържа стойност на един от неговите алтернативни типове, заедно с дискриминатор, който показва кой тип притежава в момента.
Вариантният обект съхранява този дискриминатор вътрешно, така че когато извикаме std::visit
на варианта обект, той инспектира този дискриминатор, за да определи типа на обекта.
След като std::visit
знае типа на обекта, той избира съответния функционален обект, който да извика от набора от претоварени функционални обекти, предоставени от посетителя. След това извиква този функционален обект, като предава вариантния обект като аргумент.
Така че в нашия пример, когато извикаме std::visit
на всеки елемент във вектора, той проверява дискриминатора на вариантния обект, за да определи неговия тип, и след това извиква подходящата func
функция за този тип.
Този еквивалентен код без използване на претоварване на посещения и функции може да обясни повече какво прави концептуално std::visit
.
#include <iostream> #include <vector> #include <variant> void func(int i) { std::cout << "Called func(int): " << i << std::endl; } void func(double d) { std::cout << "Called func(double): " << d << std::endl; } void func(const std::string& s) { std::cout << "Called func(string): " << s << std::endl; } int main() { std::vector<std::variant<int, double, std::string>> vec = {1, 3.14, "hello"}; for (const auto& variant : vec) { if (std::holds_alternative<int>(variant)) { int value = std::get<int>(variant); func(value); } else if (std::holds_alternative<double>(variant)) { double value = std::get<double>(variant); func(value); } else if (std::holds_alternative<std::string>(variant)) { std::string value = std::get<std::string>(variant); func(value); } } return 0; }
В този пример дефинираме три претоварени функции: func(int)
, func(double)
и func(const std::string&)
.
След това създаваме вектор от вариантни обекти, vec
, който съдържа по един елемент от всеки тип: int
, double
и std::string
.
След това итерираме всеки елемент в vec
, като използваме std::holds_alternative
, за да определим типа на текущия вариантен обект. Използваме std::get
, за да извлечем стойността на вариантния обект и след това предаваме тази стойност на подходящата функция въз основа на нейния тип.
Резюме
В обобщение, проучихме използването на std::visit
в C++, което е мощна помощна програма, която ни позволява да прилагаме различни функции към вариантен обект въз основа на текущия му тип. Като цяло публикацията в блога цели да помогне на читателите да разберат основите на std::visit и как може да се използва за безопасно преминаване през различни обекти в C++.
Кодиране на ниво нагоре
Благодарим ви, че сте част от нашата общност! Преди да тръгнеш:
- 👏 Аплодирайте за историята и следвайте автора 👉
- 📰 Вижте повече съдържание в Публикация за кодиране на ниво нагоре
- 💰 Безплатен курс за интервю за кодиране ⇒ Преглед на курса
- 🔔 Последвайте ни: Twitter | LinkedIn | Бюлетин
🚀👉 Присъединете се към колектива за таланти Level Up и намерете невероятна работа