Nano Hash - криптовалюты, майнинг, программирование

Вызов правильной свободной функции из базового указателя/ссылки

Пусть иерархия классов:

class Base { virtual ~Base() throw(); };

class DerivedA : public Base { };

class DerivedB : public Base { };

Я хотел бы иметь некоторый код, специфичный для каждого из этих производных классов. Однако этот код также специфичен для приложения, которое использует эту иерархию классов, и я не хочу встраивать этот код, специфичный для производного класса, в эти производные классы. Чтобы избежать этого, я подумал о написании бесплатных функций:

void DerivedASpecificWork( DerivedA da );

void DerivedBSpecificWork( DerivedB db );

Однако, когда мне предоставляется экземпляр производного класса через ссылку/указатель на базу, у меня нет доступа к фактическому типу экземпляра, и поэтому я не могу вызвать правильную функцию Derived*SpecificWork().

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

На самом деле, я хочу сделать это, чтобы предоставить информацию об исключении, которое произошло внутри встроенной функции, вызванной сценарием Lua. Каждое исключение несет свой собственный набор информации, то, как я хочу представить ошибку в скрипте, зависит от типа исключения. Я мог бы создать чисто виртуальный метод в базовом классе, который был бы реализован производными классами, но это потребовало бы от меня встраивания кода, связанного с Lua, в мою иерархию исключений, чего я не хочу делать, поскольку Lua специфичен для одного из приложение, использующее эту иерархию исключений.

Также я не могу использовать С++ 11.

Спасибо.


  • Однако ваш родительский деструктор является частным. 25.11.2016
  • Типичным шаблоном здесь является использование фактических виртуальных функций. Все остальное является анти-шаблоном. 25.11.2016
  • @Someprogrammerdude Да, это кажется логичным, но я бы не хотел загрязнять то, что используется в качестве библиотеки, кодом для конкретного приложения, поэтому я спрашиваю, есть ли обходные пути. 25.11.2016
  • @Virus721 Шаблон посетителя? 25.11.2016
  • @PaulMcKenzie Сначала я должен поэкспериментировать с этим, но, похоже, это то, что мне нужно. Спасибо за вашу помощь. 25.11.2016

Ответы:


1

Может быть, шаблон Brigde может вам помочь.

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

(Я не вижу вашего комментария о вашем ограничении использования С++ 11, но вы можете удалить ключевые слова std::unique_ptr, std::move и override)

class AppSpecificImp
{
public:
   virtual void DoWork() = 0;
};

class Base 
{ 
public:
   virtual ~Base() throw(); 

   virtual DoWork() = 0;
};

class DerivedA : public Base 
{ 
public:
   DerivedA(std::unique_ptr<AppSpecificImp> appImp)
      : imp(std::move(appImp))
   {
   }

   void DoWork() override
   {
       // DerivedA specific code 

       imp->DoWork();
   }

private:
   std::unique_ptr<AppSpecificImp> imp;
};

class DerivedB : public Base 
{ 
public:
   DerivedB(std::unique_ptr<AppSpecificImp> appImp)
      : imp(std::move(appImp))
   {
   }

   void DoWork() override
   {
       // DerivedB specific code 

       imp->DoWork();
   }

private:
   std::unique_ptr<AppSpecificImp> imp;
};

Изменить, чтобы показать использование шаблона посетителя:

С шаблоном посетителя вы можете делать то, что хотите, но с большим усилием.

class Visitor
{
public:
   virtual void VisitDerivedA(DerivedA* object) = 0;

   virtual void VisitDerivedB(DerivedB* object) = 0;
};

class Base
{
public:
   virtual void Visit(Visitor* visitor) = 0;
};

class DerivedA : public Base 
{ 
public:
   virtual void Visit(Visitor* visitor)
   {
       visitor->VisitDerivedA(this);
   }
};

class DerivedB : public Base 
{ 
public:
   virtual void Visit(Visitor* visitor)
   {
       visitor->VisitDerivedB(this);
   }
};

class AppSpecificVisitor : public Visitor
{
public:
   void VisitDerivedA(DerivedA* object)
   {
       // Do any work related to DerivedA class
   }

   void VisitDerivedB(DerivedB* object)
   {
       // Do any work related to DerivedB class
   }
}


int main()
{
    AppSpecificVisitor myVisitor;

    Base* myBase = // any class in your hierarchy

    myBase->Visit(&myVisitor);
}

Как я уже сказал в комментариях, с помощью шаблона посетителя вы можете добавлять новые функции без изменения основной иерархии (базовые-> производные типы). Вы просто определяете новую реализацию посетителя и пишете свою логику для каждого класса в основной иерархии. В вашем примере вы можете упаковать конкретную логику приложения в объект и ссылаться на нее в своих производных объектах, что является более простым подходом.

25.11.2016
  • Спасибо за Ваш ответ. Это немного похоже на шаблон посетителя, за исключением того, что вы сохраняете объект, специфичный для приложения, верно? 25.11.2016
  • @ Virus721 Одним из вариантов использования шаблона посетителя является двойная диспетчеризация, что означает, что выполняемая операция зависит как от вызывающего, так и от вызываемого (объект, над которым выполняется операция). Также посетитель определяет две иерархии классов: один посетитель и его реализации, а другая иерархия принимает объекты посетителей. Таким образом, вы можете добавить новую функциональность, написав новую реализацию посетителя, не изменяя другую иерархию, которая принимает посетителя. В вашем случае вам не нужна двойная отправка, вам нужен только способ внедрить логику в иерархию классов вашей библиотеки, которую делает шаблон Bridge. 25.11.2016

  • 2

    Почему бы не использовать новый набор иерархий для конкретной реализации приложения?

    class AppBase
    {
    public:
        virtual ~AppBase() throw();
        virtual void work_with_app() = 0;
    };
    
    class Base
    {
    public:
        Base(AppBase& app) : m_app(app) {}
        virtual ~Base() throw();
    protected:
        AppBase& m_app;
    };
    
    class DerivedA : public Base { DerivedA(AppBase& app) : Base(app) {} };
    
    class DerivedB : public Base { DerivedA(AppBase& app) : Base(app) {} };
    
    // Application specific implementation :
    class AppLuaSpecific : public AppBase
    {
    public:
        void work_with_app()  { /* Lua app specific */ }
    };
    

    Таким образом, ваша 1-я иерархия: Base, DerivedA, DerivedB может жить, ничего не зная о конкретном коде приложения, реализованном в AppLuaSpecific.

    25.11.2016
  • Спасибо за Ваш ответ. Это также похоже на шаблон посетителя, верно? 25.11.2016
  • @ Virus721 : Нет, это не шаблон посетителя. Это упрощенный шаблон моста: 1 экземпляр DerivedA/B может взаимодействовать с 1 экземпляром AppSpecific (который определяется при построении объекта DerivedA/B). Шаблон посетителя потребуется. Если вам нужна множественная отправка, что означает, что объект типа DerivedA/B может взаимодействовать в одном приложении с несколькими специфическими классами приложений, полученными из AppBase (что не имеет смысла, насколько я понял твои нужды). Таким образом, решение шаблона Bridge, как я предложил, должно соответствовать вашим потребностям (также предложено MohammadRB). 25.11.2016
  • Что ж, в моем приложении у меня также есть несколько способов представить (производное) исключение в скрипте Lua. Мне проще представить это как строку, содержащую всю информацию, или как несколько значений Lua. Это означает, что то, что выполняется, зависит от двух вещей: фактического типа исключения и того, как я хочу его использовать. Так что я думаю, посетитель работает лучше, если я правильно понимаю. 25.11.2016

  • 3

    Вы можете реализовать свою собственную отправку для конкретного приложения следующим образом (проверьте ее вживую на Coliru) :

    #include <iostream>
    #include <typeinfo>
    
    
    struct Base { virtual ~Base() {} };
    
    struct DerivedA : public Base { };
    
    struct DerivedB : public Base { };
    
    namespace AppSpecific
    {
    
    template<class F>
    void dispatch(const Base& b)
    {
        const std::type_info& t = typeid(b);
        if ( t == typeid(DerivedA) )
            F::doit(static_cast<const DerivedA&>(b));
        else if ( t == typeid(DerivedB) )
            F::doit(static_cast<const DerivedB&>(b));
    }
    
    struct Foo
    {
        static void doit(const DerivedA& da) { std::cout << "Foo(DerivedA)\n"; }
        static void doit(const DerivedB& db) { std::cout << "Foo(DerivedB)\n"; }
    };
    
    struct Bar
    {
        static void doit(const DerivedA& da) { std::cout << "Bar(DerivedA)\n"; }
        static void doit(const DerivedB& db) { std::cout << "Bar(DerivedB)\n"; }
    };
    
    } // namespace AppSpecific
    
    int main()
    {
        DerivedA da;
        DerivedB db;
    
        Base& b1 = da;
        Base& b2 = db;
        AppSpecific::dispatch<AppSpecific::Foo>(b1);
        AppSpecific::dispatch<AppSpecific::Foo>(b2);
        AppSpecific::dispatch<AppSpecific::Bar>(b1);
        AppSpecific::dispatch<AppSpecific::Bar>(b2);
    }
    
    25.11.2016
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..