Hello, Khabrovites. As part of the course "C ++ Developer. Professional" we have prepared a translation of the material for you.
We also invite you to an open webinar on "Scopes of Visibility and Invisibility". Participants, together with an expert, will implement a general-purpose class for an hour and a half lesson and try to run several unit tests using googletest.
Dynamic polymorphism (or runtime polymorphism) is usually associated with v-tables and virtual functions. However, in this article, I will show you a modern C ++ technique that uses std::variant
and std::visit
. This C ++ 17 technique can offer you not only better performance and value semantics, but also interesting design patterns.
: 2 2020 . ( , , ).
, , , .
, . , . (v-). , , , -. v- .
:
class Base {
public:
virtual ~Base() = default;
virtual void PrintName() const {
std::cout << "calling Bases!\n"
}
};
class Derived : public Base {
public:
void PrintName() const override {
std::cout << "calling Derived!\n"
}
};
class ExtraDerived : public Base {
public:
void PrintName() const override {
std::cout << "calling ExtraDerived!\n"
}
};
std::unique_ptr<Base> pObject = std::make_unique<Derived>();
pObject->PrintName();
? :
, .
, , .
- — .
, .
.
«». , . , . . .
?
, ( , ).
, , .
, .
C++17 ( , boost) ! .
std::variant std::visit
std::variant
, C++17, . «» std::variant
.
Base
, :
-, :
class Derived {
public:
void PrintName() const {
std::cout << "calling Derived!\n"
}
};
class ExtraDerived {
public:
void PrintName() const {
std::cout << "calling ExtraDerived!\n"
}
};
, ! .
:
std::variant<Derived, ExtraDerived> var;
var
, Derived ExtraDerived. . variant : , std::variant C ++ 17.
PrintName()
, var?
: std::visit
.
struct CallPrintName {
void operator()(const Derived& d) { d.PrintName(); }
void operator()(const ExtraDerived& ed) { ed.PrintName(); }
};
std::visit(CallPrintName{}, var);
, . std::visit
.
, (visitor) :
auto caller = [](const auto& obj) { obj.PrintName(); }
std::visit(caller, var);
«» … , ?
, :
void PrintName(std::string_view intro) const {
std::cout << intro << " calling Derived!\n;
}
- . , std::visit()
. - std::variant
( ).
— - .
struct CallPrintName {
void operator()(const Derived& d) { d.PrintName(intro); }
void operator()(const ExtraDerived& ed) { ed.PrintName(intro); }
std::string_view intro;
};
std::visit(CallPrintName{"intro text"}, var);
(visitor) -, -:
auto caller = [&intro](const auto& obj) { obj.PrintName(intro); }
std::visit(caller, var);
. ?
std::variant
,
«», .
,
(Duck typing): , , (visitor). , . . .
std::variant
, . , . , variant .
,
std::variant
. , 10 , — 100 , 100 . , 90 .
: , , , .
. .
, ,
std::visit
.
, - .
, (Label) . SimpleLabel
- , DateLabel
, , IconLabel
, .
, HTML-, :
class ILabel {
public:
virtual ~ILabel() = default;
[[nodiscard]] virtual std::string BuildHTML() const = 0;
};
class SimpleLabel : public ILabel {
public:
SimpleLabel(std::string str) : _str(std::move(str)) { }
[[nodiscard]] std::string BuildHTML() const override {
return "<p>" + _str + "</p>";
}
private:
std::string _str;
};
class DateLabel : public ILabel {
public:
DateLabel(std::string dateStr) : _str(std::move(dateStr)) { }
[[nodiscard]] std::string BuildHTML() const override {
return "<p class=\"date\">Date: " + _str + "</p>";
}
private:
std::string _str;
};
class IconLabel : public ILabel {
public:
IconLabel(std::string str, std::string iconSrc) :
_str(std::move(str)), _iconSrc(std::move(iconSrc)) { }
[[nodiscard]] std::string BuildHTML() const override {
return "<p><img src=\"" + _iconSrc + "\"/>" + _str + "</p>";
}
private:
std::string _str;
std::string _iconSrc;
};
ILabel
, , - BuildHTML.
, ILabel HTML-:
std::vector<std::unique_ptr<ILabel>> vecLabels;
vecLabels.emplace_back(std::make_unique<SimpleLabel>("Hello World"));
vecLabels.emplace_back(std::make_unique<DateLabel>("10th August 2020"));
vecLabels.emplace_back(std::make_unique<IconLabel>("Error", "error.png"));
std::string finalHTML;
for (auto &label : vecLabels)
finalHTML += label->BuildHTML() + '\n';
std::cout << finalHTML;
, BuildHTML , :
<p>Hello World</p>
<p class="date">Date: 10th August 2020</p>
<p><img src="error.png"/>Error</p>
std::variant
:
struct VSimpleLabel {
std::string _str;
};
struct VDateLabel {
std::string _str;
};
struct VIconLabel {
std::string _str;
std::string _iconSrc;
};
struct HTMLLabelBuilder {
[[nodiscard]] std::string operator()(const VSimpleLabel& label) {
return "<p>" + label._str + "</p>";
}
[[nodiscard]] std::string operator()(const VDateLabel& label) {
return "<p class=\"date\">Date: " + label._str + "</p>";
}
[[nodiscard]] std::string operator()(const VIconLabel& label) {
return "<p><img src=\"" + label._iconSrc + "\"/>" + label._str + "</p>";
}
};
Label
. , HTML- HTMLLabelBuilder
.
:
using LabelVariant = std::variant<VSimpleLabel, VDateLabel, VIconLabel>;
std::vector<LabelVariant> vecLabels;
vecLabels.emplace_back(VSimpleLabel { "Hello World"});
vecLabels.emplace_back(VDateLabel { "10th August 2020"});
vecLabels.emplace_back(VIconLabel { "Error", "error.png"});
std::string finalHTML;
for (auto &label : vecLabels)
finalHTML += std::visit(HTMLLabelBuilder{}, label) + '\n';
std::cout << finalHTML;
HTMLLabelBuilder
— , . , - :
struct VSimpleLabel {
[[nodiscard]] std::string BuildHTML() const {
return "<p class=\"date\">Date: " + _str + "</p>";
}
std::string _str;
};
struct VDateLabel {
[[nodiscard]] std::string BuildHTML() const {
return "<p class=\"date\">Date: " + _str + "</p>";
}
std::string _str;
};
struct VIconLabel {
[[nodiscard]] std::string BuildHTML() const {
return "<p><img src=\"" + _iconSrc + "\"/>" + _str + "</p>";
}
std::string _str;
std::string _iconSrc;
};
auto callBuildHTML = [](auto& label) { return label.BuildHTML(); };
for (auto &label : vecLabels)
finalHTML += std::visit(callBuildHTML, label) + '\n'
, , .
(Concepts)
std::variant/std::visit
, . , . , C++20 , , .
( Mariusz J )
template <typename T>
concept ILabel = requires(const T v)
{
{v.buildHtml()} -> std::convertible_to<std::string>;
};
, - buildHtml()
, , std::string
.
( constrained auto
):
auto callBuildHTML = [](ILabel auto& label) -> std::string { return label.buildHtml(); };
for (auto &label : vecLabels)
finalHTML += std::visit(callBuildHTML, label) + '\n';
, std::variant
.
:
unique_ptr std::variant C++17 -
, , , , .
std::visit
, ?
.
ILabel
, .
, ; , , .
, .
using ABC = std::variant<AParticle, BParticle, CParticle>;
std::vector<ABC> particles(PARTICLE_COUNT);
for (std::size_t i = 0; auto& p : particles) {
switch (i%3) {
case 0: p = AParticle(); break;
case 1: p = BParticle(); break;
case 2: p = CParticle(); break;
}
++i;
}
auto CallGenerate = [](auto& p) { p.generate(); };
for (auto _ : state) {
for (auto& p : particles)
std::visit(CallGenerate, p);
}
Particle
( AParticle, BParticle . .) 72 , Generate()
, «».
10% std::visit
!
? , :
variant , . .
, , , , v-.
, , variant 20% , : td::vector particles(PARTICLE_COUNT);
. QuickBench
, std::visit
. , TCPIP std::visit
. , .
, - std::visit
. , , .
, :
, std::variant
, , «» . , , .
std::visit std::variant
, , .
: GCC 10.1, C++17, O2:
! , — 39k 44k. , 2790 LOC 1945 LOC .
.
, C++, .
. std::variant
, — , . std::visit
, , .
std::variant
«» ? , . , std::variant
, , - . , , std::variant
, .
, . . ( Github):
std::variant/std::visit . , - , . , , . / , , . variant ( , ) .
, , , ( , move ), , , - . , . , , - . , callback hell. , , .
:
std::variant
std::visit
?
?
.
"C++ Developer. Professional".