Halo, orang Khabrov. Sebagai bagian dari kursus "Pengembang C ++. Profesional", kami telah menyiapkan terjemahan materi untuk Anda.
Kami juga mengundang Anda ke webinar terbuka tentang "Cakupan Visibilitas dan Gaib". Peserta, bersama dengan seorang ahli, akan menerapkan kelas tujuan umum selama satu setengah jam pelajaran dan mencoba menjalankan beberapa tes unit menggunakan googletest.


Polimorfisme dinamis (atau polimorfisme runtime) biasanya dikaitkan dengan v-tabel dan fungsi virtual. Namun, dalam artikel ini, saya akan menunjukkan kepada Anda teknik C ++ modern yang menggunakan std::variant
dan std::visit
. Teknik C ++ 17 ini tidak hanya menawarkan performa dan nilai semantik yang lebih baik, tetapi juga pola desain yang menarik.
: 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;
Coliru.
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';
@Wandbox.
, std::variant
.
:
unique_ptr std::variant C++17 -
, , , , .
std::visit
, ?
.
ILabel
, .
@QuickBench.
, ; , , .
, .
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
. , .
CppCon 2018: « std::variant»
, - std::visit
. , , .
, :
V2 –
- std::variant? std::visit fault : r/cpp
- std::variant? std::visit fault (Part 2) : r/cpp
, std::variant
, , «» . , , .
std::visit std::variant
, , .
@BuildBench
: 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".
« ».