Автоматический обход иерархии
От: Kazmerchuk Pavel  
Дата: 14.09.22 11:50
Оценка: 1 (1)
Добрый день.

Есть иерархия классов от которой наследуется пользователь https://godbolt.org/z/nTdPcdEM4

#include <iostream>

struct A {
 void virtual f() = 0;
};
struct B : A {
    void f() override {
        std::cout << "B::f()\n";
    }
};
struct C : B {
    void f() override {
        B::f();
        std::cout << "C::f()\n";
    }
};
struct User1 : C {
    void f() override {
        //C::f();
        std::cout << "User1::f()\n";
    }
};
struct User2 : User1 {
    void f() override {
        //User1::f();
        std::cout << "User2::f()\n";
    }
};
int main()
{
    User2 u2;
    A* obj = &u2;
    obj->f();
    return 0;
}


Если пользователь переопределяет виртуальную функцию он должен вызвать эту же функцию из базового класса.
Можно ли сделать это автоматически или хотя бы выдать ошибку если пользователь забыл вызвать эту функцию из базы.
В классы A,B,C можно добавлять любой код.
Re: Автоматический обход иерархии
От: so5team https://stiffstream.com
Дата: 14.09.22 12:02
Оценка: +1
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Если пользователь переопределяет виртуальную функцию он должен вызвать эту же функцию из базового класса.

KP>Можно ли сделать это автоматически или хотя бы выдать ошибку если пользователь забыл вызвать эту функцию из базы.
KP>В классы A,B,C можно добавлять любой код.

Единственный вариант, который сходу приходит в голову, но вряд ли вам такое подойдет:

#include <iostream>

struct A {
    class only_A_can_make_me {
        friend class A;
        only_A_can_make_me() {}
    public:
    };

    virtual only_A_can_make_me f() { return {}; };
};
struct B : A {
    A::only_A_can_make_me f() override {
        std::cout << "B::f()\n";
        return A::f();
    }
};
struct C : B {
    A::only_A_can_make_me f() override {
        auto r = B::f();
        std::cout << "C::f()\n";
        return r;
    }
};
struct User1 : C {
    A::only_A_can_make_me f() override {
        //C::f();
        std::cout << "User1::f()\n";
    }
};
struct User2 : User1 {
    A::only_A_can_make_me f() override {
        //User1::f();
        std::cout << "User2::f()\n";
    }
};
int main()
{
    User2 u2;
    A* obj = &u2;
    obj->f();
    return 0;
}
Re: Автоматический обход иерархии
От: Went  
Дата: 14.09.22 12:06
Оценка:
Здравствуйте, Kazmerchuk Pavel, Вы писали:
KP>Если пользователь переопределяет виртуальную функцию он должен вызвать эту же функцию из базового класса.
KP>Можно ли сделать это автоматически или хотя бы выдать ошибку если пользователь забыл вызвать эту функцию из базы.
KP>В классы A,B,C можно добавлять любой код.
Наследуемых пользователем классов и переопределяемых функций много?
Re: Автоматический обход иерархии
От: DiPaolo Россия  
Дата: 14.09.22 12:08
Оценка:
KP>Есть иерархия классов от которой наследуется пользователь https://godbolt.org/z/nTdPcdEM4

А вот за это спасибо Удобно

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

#include <iostream>

struct A {
  void f() {implementMeA();}
  virtual void implementMeA() {}
};
struct B : A {
    void implementMeA() override {
        std::cout << "B::f()\n";
        implementMeB();
    }
    virtual void implementMeB() {}
};
struct C : B {
    void implementMeB() override {
        std::cout << "C::f()\n";
        implementMeC();
    }
    virtual void implementMeC() {}
};
struct User1 : C {
    void implementMeC() override {
        std::cout << "User1::f()\n";
        implementMeUser1();
    }
    virtual void implementMeUser1() {}
};
struct User2 : User1 {
    void implementMeUser1() override {
        std::cout << "User2::f()\n";
    }
};

struct CustomerDeclaredObj : C {

};

struct AnotherCustomerDeclaredObj : C {
        void implementMeC() override {
        std::cout << "AnotherCustomerDeclaredObj::f()\n";
        implementMeAnotherCustomerDeclaredObj();
    }
    virtual void implementMeAnotherCustomerDeclaredObj() {}
};

int main()
{
    User1 u2;
    A* obj = &u2;
    obj->f();
    std::cout << std::endl;

    CustomerDeclaredObj cdo;
    A* customerObj = &cdo;
    customerObj->f();
    std::cout << std::endl;

    AnotherCustomerDeclaredObj acdo;
    A* anCustomerObj = &acdo;
    anCustomerObj->f();
    std::cout << std::endl;

    return 0;
}


https://godbolt.org/z/v9K3GaMj8
Патриот здравого смысла
Re[2]: Автоматический обход иерархии
От: Kazmerchuk Pavel  
Дата: 14.09.22 12:15
Оценка:
Здравствуйте, Went, Вы писали:

W>Наследуемых пользователем классов и переопределяемых функций много?


Классов произвольное количество, функций две.
Re[3]: Автоматический обход иерархии
От: Went  
Дата: 14.09.22 12:23
Оценка: 6 (1)
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Классов произвольное количество, функций две.

Можно написать шаблон класса-прокладки, который будет корректно вызывать нужные функции, а наследникам предоставлять другие функции для перекрытия:
// Код библиотеки
class A
{
protected:
  virtual void foo();
  virtual void bar();
};

template<class Base>
class UserInheritedProxy : public Base
{
protected:
  virtual void do_foo() {}
  virtual void do_bar() {}

private:
  virtual void foo() {Base::foo(); do_foo();}
  virtual void bar() {Base::bar(); do_bar();}
};

// Код пользователя
class User1 : UserInheritedProxy<A>
{
protected:
  virtual void do_foo() {UserInheritedProxy<A>::do_foo();} // Могу базу звать
  virtual void do_bar() {} // А могу не звать, результат одинаков.
}
Отредактировано 14.09.2022 12:29 Went . Предыдущая версия .
Re: Автоматический обход иерархии
От: Videoman Россия https://hts.tv/
Дата: 14.09.22 14:04
Оценка: +4
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Можно ли сделать это автоматически или хотя бы выдать ошибку если пользователь забыл вызвать эту функцию из базы.


Такая проблема возникает, обычно, когда иерархия спроектирована не аккуратно и переопределяемая функция выполнят не только расширяемый функционал, но и часть базового. Я всегда использую идиому NVI (Non-Virtual Interface) в таких случаях. Подход зависит от задачи, но идeя следующая:
#include <iostream>

struct base 
{
    void f() 
    { 
        std::cout<< "Base prolog\n";
        specific();
        std::cout<< "Base epilog\n";
    }

private:

    virtual void specific()
    {
        std::cout<< "Base specific\n";
    }
};

struct derived : base
{
private:

    virtual void specific()
    {
        std::cout<< "Derived specific\n";
    }
};

int main()
{
    base b;
    b.f();
    
    derived d;
    d.f();
    
    return 0;
}
Отредактировано 14.09.2022 14:06 Videoman . Предыдущая версия . Еще …
Отредактировано 14.09.2022 14:05 Videoman . Предыдущая версия .
Re[4]: Автоматический обход иерархии
От: Kazmerchuk Pavel  
Дата: 14.09.22 15:34
Оценка:
Здравствуйте, Went, Вы писали:

W>Можно написать шаблон класса-прокладки, который будет корректно вызывать нужные функции, а наследникам предоставлять другие функции для перекрытия:

Что помешает юзеру наследоваться от User1?
class User2 : User1 {};
Re[2]: Автоматический обход иерархии
От: Kazmerchuk Pavel  
Дата: 14.09.22 15:39
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Такая проблема возникает, обычно, когда иерархия спроектирована не аккуратно и переопределяемая функция выполнят не только расширяемый функционал, но и часть базового. Я всегда использую идиому NVI (Non-Virtual Interface) в таких случаях.

Про NVI знаю, но это не мой случай. Например, мне нужно сериализовать сложный объект. Каждый производный класс добавляет свои сериализуемые данные. Как такое обычно решается?
Re[3]: Автоматический обход иерархии
От: Videoman Россия https://hts.tv/
Дата: 14.09.22 16:06
Оценка:
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Про NVI знаю, но это не мой случай. Например, мне нужно сериализовать сложный объект. Каждый производный класс добавляет свои сериализуемые данные. Как такое обычно решается?


Ну это уже совершенно другая задача же. Абстрактно, сериализация это набор вложенных друг в друга структур (классов и т.д.), в общем виде даже не унаследованных от общего предка. Вариантов масса. У меня, например, просто заводится два свободных шаблонных метода, условно, Pack, Unpack для каждого поддерживаемого типа. За сериализацию отвечает родитель, тот, кто владеет вложенной структурой. Также есть объект, условно, Archive, который определяет сам формат сериализации (Json, XML, Bibary и т.д.). О передается рекурсивно по всей иерархии и ему передаются нужные данные. Не очень понятно зачем тут нужны виртуальные функции
Re[4]: Автоматический обход иерархии
От: Kazmerchuk Pavel  
Дата: 14.09.22 16:15
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Ну это уже совершенно другая задача же. Абстрактно, сериализация это набор вложенных друг в друга структур (классов и т.д.), в общем виде даже не унаследованных от общего предка. Вариантов масса. У меня, например, просто заводится два свободных шаблонных метода, условно, Pack, Unpack для каждого поддерживаемого типа. За сериализацию отвечает родитель, тот, кто владеет вложенной структурой. Также есть объект, условно, Archive, который определяет сам формат сериализации (Json, XML, Bibary и т.д.). О передается рекурсивно по всей иерархии и ему передаются нужные данные. Не очень понятно зачем тут нужны виртуальные функции

Ну хорошо, пусть не виртуальные. Иерархию нужно обойти. Сериализовать нужно через указатель на базовый класс. Хочется избавить пользователя от необходимости дергать сериализацию родителя или хотя бы "сказать" ему что он забыл это сделать.
Re[5]: Автоматический обход иерархии
От: Went  
Дата: 14.09.22 16:33
Оценка:
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Что помешает юзеру наследоваться от User1?

Да ему и от A ничто не помешает наследоваться. С++ не делает разницы между "юзером" и "админом". Если разработчик библиотеки может наследоваться от А, то заставить человека наследоваться только через прокси-класс можно только уговорами
Re[5]: Автоматический обход иерархии
От: Sm0ke Россия ksi
Дата: 14.09.22 16:43
Оценка:
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Здравствуйте, Videoman, Вы писали:


V>>Ну это уже совершенно другая задача же. Абстрактно, сериализация это набор вложенных друг в друга структур (классов и т.д.), в общем виде даже не унаследованных от общего предка. Вариантов масса. У меня, например, просто заводится два свободных шаблонных метода, условно, Pack, Unpack для каждого поддерживаемого типа. За сериализацию отвечает родитель, тот, кто владеет вложенной структурой. Также есть объект, условно, Archive, который определяет сам формат сериализации (Json, XML, Bibary и т.д.). О передается рекурсивно по всей иерархии и ему передаются нужные данные. Не очень понятно зачем тут нужны виртуальные функции

KP>Ну хорошо, пусть не виртуальные. Иерархию нужно обойти. Сериализовать нужно через указатель на базовый класс. Хочется избавить пользователя от необходимости дергать сериализацию родителя или хотя бы "сказать" ему что он забыл это сделать.

В базовом классе завести свойство вектор статических членов-функций сериализации (тут можно std::function или указатель на функцию), которые принимают указатель на базовый класс и кастуют его к своему классу.
В каждом конструкторе иерархии добавлять по своей функции в этот вектор.
Потом в базовом классе сделать метод, обходящий этот вектор и вызывающий все функции.
Re[3]: Автоматический обход иерархии
От: Went  
Дата: 14.09.22 16:52
Оценка:
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Про NVI знаю, но это не мой случай. Например, мне нужно сериализовать сложный объект. Каждый производный класс добавляет свои сериализуемые данные. Как такое обычно решается?

Делаете статическую библиотеку сериализации. Грубо говоря, мапу от типа объекта (typeid или какой-то уникальный индекс) на некий сериализатор только его членов. Это может быть пара свободных функций или некое перечисление полей. В функции регистрации сериализатора обязательно требуйте какое-то указание ид объекта-родителя, чтобы сериализатор не забыл его члены, и так по цепочке. Во время сериализации, если система не находит зарегистрированного на этот тип сериализатора, происходит ошибка. Всё.
Re[3]: Автоматический обход иерархии
От: AleksandrN Россия  
Дата: 14.09.22 19:49
Оценка:
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Например, мне нужно сериализовать сложный объект. Каждый производный класс добавляет свои сериализуемые данные. Как такое обычно решается?


Посмотри на готовые форматы сериализации и генераторы кода для них. Например: protobuf, Apache Thrift, Apache Avro. Может окажутся подходящими для твоих задач, может в их исходниках интересные идеи найдёшь.
Re[5]: Автоматический обход иерархии
От: Videoman Россия https://hts.tv/
Дата: 14.09.22 20:35
Оценка:
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>Ну хорошо, пусть не виртуальные. Иерархию нужно обойти. Сериализовать нужно через указатель на базовый класс. Хочется избавить пользователя от необходимости дергать сериализацию родителя или хотя бы "сказать" ему что он забыл это сделать.


Вот тут я уже перестаю понимать, зачем нужен общий базовый класс у сериализуемых объектов и зачем сериализовывать шиворот на выворот. Как предполагается тогда сериализовывать объекты у которых принципиально базового класса быть не может, типа такого: std::map<User, std::vector<SomeObject>> ?
Re: Автоматический обход иерархии
От: kov_serg Россия  
Дата: 14.09.22 22:07
Оценка:
Здравствуйте, Kazmerchuk Pavel, Вы писали:

KP>В классы A,B,C можно добавлять любой код.

https://godbolt.org/z/ba5EerK3b
#include <iostream>

template<class P> struct Extends : P { typedef P inherited; };

struct A {
    void virtual f() = 0;
};
struct B : A {
    void f() override {
        std::cout << "B::f()\n";
    }
};
struct C : Extends<B> {
    void f() override {
        inherited::f();
        std::cout << "C::f()\n";
    }
};
struct User1 : Extends<C> {
    void f() override {
        inherited::f();
        std::cout << "User1::f()\n";
    }
};
struct User2 : Extends<User1> {
    void f() override {
        inherited::f();
        std::cout << "User2::f()\n";
    }
};
int main() {
    User2 u2;
    A* obj = &u2;
    obj->f();
    return 0;
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.