ПрограммированиеФорумОбщее

Очень интересный вопрос по С++

#0
2:29, 7 июля 2016

Здравствуй, 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();
  }

};

Буду благодарен любым советам)

#1
5:50, 7 июля 2016

Откуда всякие override и что такое $ ? :)
Но если речь про С++, то std::function позволяет реализовать произвольные кэллбэки, в т.ч. методы объектов (делегаты), так что механизм onObjectChanged() делается легко и безотносительно наследований.

#2
7:35, 7 июля 2016

ialexbr
> Eсли на одного наблюдателя ссылаются много реализаций, то все они будут хранить
> указатель и тратить лишнюю память, этого хотелось бы избежать, возможно есть
> способ добиться нужного поведения через шаблоны, например?
Если вопрос только в памяти, выводи перед сном в консоль каждый раз sizeof(void*) и спи спокойно. Зачем городить огород ради 4-8 байт? Меньше указателя может весить только 1 байт. Но оно того не стоит.

#3
8:33, 7 июля 2016

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;
>
> };
Изображение

#4
9:09, 7 июля 2016

ialexbr
> $object;

это не есть с++

ialexbr
> Eсли на одного наблюдателя ссылаются много реализаций, то все они будут хранить
> указатель и тратить лишнюю память

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

ialexbr
> этого хотелось бы избежать

не получится.
это не возможно.

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

оби альтернативы не отвечают вашим хотелкам, и ущербы.

#5
9:51, 7 июля 2016

Kartonagnick
> это не есть с++
Это валидно в студии.

PS: Пост не осилил

#6
11:42, 7 июля 2016

-Eugene-
"Студия" это не есть С++!!! Там много чего валидно, но это не означает, что все нужно использовать в своем коде.

#7
14:40, 7 июля 2016

=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
> ваши альтернативы:
> передавать обсервера в каждый метод, где он нужен.
> или в качестве обсервера использовать глобальный синглетон.
Думал об этом, но..
> оби альтернативы не отвечают вашим хотелкам, и ущербы.

#8
14:52, 7 июля 2016

ialexbr
> На счет валидности кода, на сколько я знаю знак $ является для компилятора
> таким же как и знак _
Нет

#9
14:55, 7 июля 2016

ialexbr
> и написал бы _object

это - UB

#10
15:19, 7 июля 2016

tvorez
> "Студия" это не есть С++!!!
  g++, видимо, тоже: http://ideone.com/U7iPZs

#11
15:37, 7 июля 2016

2.11 Identifiers
    identifier:
        identifier-nondigit
        identifier identifier-nondigit
        identifier digit
    identifier-nondigit:
        nondigit
        universal-character-name
        other implementation-defined characters

ПрограммированиеФорумОбщее

Тема в архиве.