Polymorphisme dynamique utilisant std :: variant et std :: visit

Bonjour, Khabrovites. Dans le cadre du cours "Développeur C ++. Professionnel", nous avons préparé une traduction du matériel pour vous.



Nous vous invitons également à un webinaire ouvert sur les
"Scopes of Visibility and Invisibility". Les participants, avec un expert, mettront en œuvre une classe polyvalente pendant une heure et demie de leçon et essaieront d'exécuter plusieurs tests unitaires à l'aide de googletest.






Le polymorphisme dynamique (ou polymorphisme d'exécution) est généralement associé aux v-tables et aux fonctions virtuelles. Cependant, dans cet article, je vais vous montrer une technique C ++ moderne qui utilise std::variant



et std::visit



. Cette technique C ++ 17 peut vous offrir non seulement de meilleures performances et une meilleure sémantique, mais également des modèles de conception intéressants.





: 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".





« ».








All Articles