Здравствуй, gamedev!
Сразу к делу.
Был такой код (упрощенный):
class Object { /* Реализация класса */ };
class Interface { public: virtual Object& object() = 0; };
class Implementation: public Interface { public: virtual Object& object() override { return $object; } virtual void object( const Object& object) { this->object( ) = object; } virtual void object( Object&& object) { this->object( ) = std::move( object); } private: Object $object; };
Затем этот код использовался примерно так:
class Test: public Implementation { /* Реализация класса */ };
Test test; test.object(Object{}); /*какой-то код*/ Object object = test.object( ); test.object( ).вызов_метода( );
Очень удобно использовать при наследовании, не приходится каждый раз писать функции из Implementation.
Но возникает 2 проблемы:
1. Такой подход ограничен только наследованием;
2. Хотелось бы сделать что-то похожее на реактивное программирование, т.е. чтобы классу Implementation можно было передать наблюдателя (Observer) и чтобы у наблюдателя автоматически вызывался метод onObjectChanged. Примерно так:
class Observer { public: void onObjectChanged() {} };
class Implementation: public Interface { public: Implementation(const Observer& observer): observer{&observer} {} virtual Object& object( ) override { return $object; } virtual void object( const Object& object) { this->object( ) = object; observer->onObjectChanged( ); } virtual void object( Object&& object) { this->object( ) = std::move( object); observer->onObjectChanged( ); } private: Observer* observer; Object $object; };
Да можно в конструкторе принимать наблюдателя и сохранять в классе Implementation, а затем в нужные моменты вызывать нужный метод.
НО представьте такую ситуацию:
class Observer { public: void onObjectChanged() {} };
class Test { private: Observer observer; Implementation impl1{observer}; Implementation impl2{observer}; Implementation impl3{observer}; Implementation impl4{observer}; Implementation impl5{observer}; Implementation impl6{observer}; };
Eсли на одного наблюдателя ссылаются много реализаций, то все они будут хранить указатель и тратить лишнюю память, этого хотелось бы избежать, возможно есть способ добиться нужного поведения через шаблоны, например? Собственно в этом и заключается вопрос, как реализовать что-то подобное реактивному программированию наилучшим способом, и c возможностью не наследоваться от класса реализации, а передавать ему классы интерфейсов и наблюдателей?
Хочется что-то такое (нерабочий псевдокод):
template <class INTERFACE, class OBSERVER> class Implementation { public: Implementation(const Observer& observer): observer{&observer} {} virtual Object& object( ) override { return INTERFACE::object( ); } virtual void object( const Object& object) { INTERFACE::object( ) = object; OBSERVER::onObjectChanged( ); } virtual void object( Object&& object) { INTERFACE::object( ) = std::move( object); OBSERVER::onObjectChanged( ); } };
Буду благодарен любым советам)
Откуда всякие override и что такое $ ? :)
Но если речь про С++, то std::function позволяет реализовать произвольные кэллбэки, в т.ч. методы объектов (делегаты), так что механизм onObjectChanged() делается легко и безотносительно наследований.
ialexbr
> Eсли на одного наблюдателя ссылаются много реализаций, то все они будут хранить
> указатель и тратить лишнюю память, этого хотелось бы избежать, возможно есть
> способ добиться нужного поведения через шаблоны, например?
Если вопрос только в памяти, выводи перед сном в консоль каждый раз sizeof(void*) и спи спокойно. Зачем городить огород ради 4-8 байт? Меньше указателя может весить только 1 байт. Но оно того не стоит.
ialexbr
> class Implementation: public Interface {
> public:
>
> virtual Object& object() override {
> return $object;
> }
>
> virtual void object(const Object& object) {
> this->object() = object;
> }
>
> virtual void object(Object&& object) {
> this->object() = std::move(object);
> }
>
> private:
>
> Object $object;
>
> };

ialexbr
> $object;
это не есть с++
ialexbr
> Eсли на одного наблюдателя ссылаются много реализаций, то все они будут хранить
> указатель и тратить лишнюю память
если вы хотите, что бы из разных методов, объект мог звать другой объект,
тогда он должен знать о нем.
и тогда это уже не лишняя память.
это - здравый смысл.
ialexbr
> этого хотелось бы избежать
не получится.
это не возможно.
ваши альтернативы:
передавать обсервера в каждый метод, где он нужен.
или в качестве обсервера использовать глобальный синглетон.
оби альтернативы не отвечают вашим хотелкам, и ущербы.
Kartonagnick
> это не есть с++
Это валидно в студии.
PS: Пост не осилил
-Eugene-
"Студия" это не есть С++!!! Там много чего валидно, но это не означает, что все нужно использовать в своем коде.
=A=L=X=
> Откуда всякие override
override из С++11
=A=L=X=
> что такое $ ? :)
Kartonagnick
> > $object;
> это не есть с++
-Eugene-
> Это валидно в студии.
tvorez
> "Студия" это не есть С++!!! Там много чего валидно, но это не означает, что все нужно использовать в своем коде.
Знак $ я использую только для того, чтобы переменная object имела название отличное от названий функций в этом же классе, иначе компилятор начинает негодовать. По сути это тоже самое, как если бы я использовал устоявшиеся варианты и написал бы _object или mObject, просто мне эти варианты не нравятся тем, что при использовании нижнего подчеркивания код выглядит менее читаемым (для меня), а при использовании m в подсказках IDE такие переменные находятся в середине списка подсказок.
На счет валидности кода, на сколько я знаю знак $ является для компилятора таким же как и знак _ и должен абсолютно нормально восприниматься всеми компиляторами и IDE. Xcode, CLion, Android Studio и Visual Studio это подтверждают. Ну а если кому-то хочется выколоть себе глаза при виде этого знака, то это вопрос вкусовщины)
bool
> Зачем городить огород ради 4-8 байт? Меньше указателя может весить только 1
> байт. Но оно того не стоит.
При множественном наследовании от таких объектов и от других, конечный объект может весить не мало. Да и вообще хотел сделать контейнер, который бы вел себя "реактивно", т.е. сообщал бы наблюдателю, что данные изменились, но не хотелось бы чтобы контейнер весил больше трех указателей и от него приходилось бы наследоваться :D
Kartonagnick
> если вы хотите, что бы из разных методов, объект мог звать другой объект,
> тогда он должен знать о нем.
> и тогда это уже не лишняя память.
> это - здравый смысл.
Логично.
Kartonagnick
> ваши альтернативы:
> передавать обсервера в каждый метод, где он нужен.
> или в качестве обсервера использовать глобальный синглетон.
Думал об этом, но..
> оби альтернативы не отвечают вашим хотелкам, и ущербы.
ialexbr
> На счет валидности кода, на сколько я знаю знак $ является для компилятора
> таким же как и знак _
Нет
ialexbr
> и написал бы _object
это - UB
tvorez
> "Студия" это не есть С++!!!
g++, видимо, тоже: http://ideone.com/U7iPZs
2.11 Identifiers
identifier:
identifier-nondigit
identifier identifier-nondigit
identifier digit
identifier-nondigit:
nondigit
universal-character-name
other implementation-defined characters
Тема в архиве.