Здравствуйте, Kazmerchuk Pavel, Вы писали:
KP>Можно ли сделать это автоматически или хотя бы выдать ошибку если пользователь забыл вызвать эту функцию из базы.
Такая проблема возникает, обычно, когда иерархия спроектирована не аккуратно и переопределяемая функция выполнят не только расширяемый функционал, но и часть базового. Я всегда использую идиому NVI (Non-Virtual Interface) в таких случаях. Подход зависит от задачи, но идeя следующая:
Здравствуйте, 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() {} // А могу не звать, результат одинаков.
}
Если пользователь переопределяет виртуальную функцию он должен вызвать эту же функцию из базового класса.
Можно ли сделать это автоматически или хотя бы выдать ошибку если пользователь забыл вызвать эту функцию из базы.
В классы A,B,C можно добавлять любой код.
Здравствуйте, 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;
}
Здравствуйте, Kazmerchuk Pavel, Вы писали: KP>Если пользователь переопределяет виртуальную функцию он должен вызвать эту же функцию из базового класса. KP>Можно ли сделать это автоматически или хотя бы выдать ошибку если пользователь забыл вызвать эту функцию из базы. KP>В классы A,B,C можно добавлять любой код.
Наследуемых пользователем классов и переопределяемых функций много?
Здравствуйте, Went, Вы писали:
W>Можно написать шаблон класса-прокладки, который будет корректно вызывать нужные функции, а наследникам предоставлять другие функции для перекрытия:
Что помешает юзеру наследоваться от User1?
Здравствуйте, Videoman, Вы писали:
V>Такая проблема возникает, обычно, когда иерархия спроектирована не аккуратно и переопределяемая функция выполнят не только расширяемый функционал, но и часть базового. Я всегда использую идиому NVI (Non-Virtual Interface) в таких случаях.
Про NVI знаю, но это не мой случай. Например, мне нужно сериализовать сложный объект. Каждый производный класс добавляет свои сериализуемые данные. Как такое обычно решается?
Здравствуйте, Kazmerchuk Pavel, Вы писали:
KP>Про NVI знаю, но это не мой случай. Например, мне нужно сериализовать сложный объект. Каждый производный класс добавляет свои сериализуемые данные. Как такое обычно решается?
Ну это уже совершенно другая задача же. Абстрактно, сериализация это набор вложенных друг в друга структур (классов и т.д.), в общем виде даже не унаследованных от общего предка. Вариантов масса. У меня, например, просто заводится два свободных шаблонных метода, условно, Pack, Unpack для каждого поддерживаемого типа. За сериализацию отвечает родитель, тот, кто владеет вложенной структурой. Также есть объект, условно, Archive, который определяет сам формат сериализации (Json, XML, Bibary и т.д.). О передается рекурсивно по всей иерархии и ему передаются нужные данные. Не очень понятно зачем тут нужны виртуальные функции
Здравствуйте, Videoman, Вы писали:
V>Ну это уже совершенно другая задача же. Абстрактно, сериализация это набор вложенных друг в друга структур (классов и т.д.), в общем виде даже не унаследованных от общего предка. Вариантов масса. У меня, например, просто заводится два свободных шаблонных метода, условно, Pack, Unpack для каждого поддерживаемого типа. За сериализацию отвечает родитель, тот, кто владеет вложенной структурой. Также есть объект, условно, Archive, который определяет сам формат сериализации (Json, XML, Bibary и т.д.). О передается рекурсивно по всей иерархии и ему передаются нужные данные. Не очень понятно зачем тут нужны виртуальные функции
Ну хорошо, пусть не виртуальные. Иерархию нужно обойти. Сериализовать нужно через указатель на базовый класс. Хочется избавить пользователя от необходимости дергать сериализацию родителя или хотя бы "сказать" ему что он забыл это сделать.
Здравствуйте, Kazmerchuk Pavel, Вы писали:
KP>Что помешает юзеру наследоваться от User1?
Да ему и от A ничто не помешает наследоваться. С++ не делает разницы между "юзером" и "админом". Если разработчик библиотеки может наследоваться от А, то заставить человека наследоваться только через прокси-класс можно только уговорами
Здравствуйте, Kazmerchuk Pavel, Вы писали:
KP>Здравствуйте, Videoman, Вы писали:
V>>Ну это уже совершенно другая задача же. Абстрактно, сериализация это набор вложенных друг в друга структур (классов и т.д.), в общем виде даже не унаследованных от общего предка. Вариантов масса. У меня, например, просто заводится два свободных шаблонных метода, условно, Pack, Unpack для каждого поддерживаемого типа. За сериализацию отвечает родитель, тот, кто владеет вложенной структурой. Также есть объект, условно, Archive, который определяет сам формат сериализации (Json, XML, Bibary и т.д.). О передается рекурсивно по всей иерархии и ему передаются нужные данные. Не очень понятно зачем тут нужны виртуальные функции KP>Ну хорошо, пусть не виртуальные. Иерархию нужно обойти. Сериализовать нужно через указатель на базовый класс. Хочется избавить пользователя от необходимости дергать сериализацию родителя или хотя бы "сказать" ему что он забыл это сделать.
В базовом классе завести свойство вектор статических членов-функций сериализации (тут можно std::function или указатель на функцию), которые принимают указатель на базовый класс и кастуют его к своему классу.
В каждом конструкторе иерархии добавлять по своей функции в этот вектор.
Потом в базовом классе сделать метод, обходящий этот вектор и вызывающий все функции.
Здравствуйте, Kazmerchuk Pavel, Вы писали:
KP>Про NVI знаю, но это не мой случай. Например, мне нужно сериализовать сложный объект. Каждый производный класс добавляет свои сериализуемые данные. Как такое обычно решается?
Делаете статическую библиотеку сериализации. Грубо говоря, мапу от типа объекта (typeid или какой-то уникальный индекс) на некий сериализатор только его членов. Это может быть пара свободных функций или некое перечисление полей. В функции регистрации сериализатора обязательно требуйте какое-то указание ид объекта-родителя, чтобы сериализатор не забыл его члены, и так по цепочке. Во время сериализации, если система не находит зарегистрированного на этот тип сериализатора, происходит ошибка. Всё.
Здравствуйте, Kazmerchuk Pavel, Вы писали:
KP>Например, мне нужно сериализовать сложный объект. Каждый производный класс добавляет свои сериализуемые данные. Как такое обычно решается?
Посмотри на готовые форматы сериализации и генераторы кода для них. Например: protobuf, Apache Thrift, Apache Avro. Может окажутся подходящими для твоих задач, может в их исходниках интересные идеи найдёшь.
Здравствуйте, Kazmerchuk Pavel, Вы писали:
KP>Ну хорошо, пусть не виртуальные. Иерархию нужно обойти. Сериализовать нужно через указатель на базовый класс. Хочется избавить пользователя от необходимости дергать сериализацию родителя или хотя бы "сказать" ему что он забыл это сделать.
Вот тут я уже перестаю понимать, зачем нужен общий базовый класс у сериализуемых объектов и зачем сериализовывать шиворот на выворот. Как предполагается тогда сериализовывать объекты у которых принципиально базового класса быть не может, типа такого: std::map<User, std::vector<SomeObject>> ?