Re[10]: как сократить количество vtbl
От: qaz77  
Дата: 26.04.17 08:15
Оценка:
Идея с NVI и запросом интерфейса через приведение указателей на несовместимые типы мне понятна.
Что using применяется для "обезжиривания" интерфейса — тоже понятно.

Чисто субъективно, мне не нравятся операторы приведения, я бы сделал функции типа asSerializable() или querySerializable()
для явного запроса.

Но суть моих сомнений в другом.
Все хорошо получается, когда рассматриваем один класс CBar и жирный интерфейс ISuperBar.
Попробуем распространить идею на два класса CCat и СDog.
Оба должны быть поддерживать (каким-то способом) ISerializable и полиморфно обрабатываться в foo(ISerializable*).

Итак, как нам объявить ISerializable?
Так:
////////////////////////////
// Serializable.h

#include <SuperCat.h>
struct ISerializable : ISuperCat {
using ISuperCat::Serialize;
};

Или так:
////////////////////////////
// Serializable.h

#include <SuperDog.h>
struct ISerializable : ISuperDog {
using ISuperDog::Serialize;
};


Выход я вижу только в объявлении интерфейса собакота.
А в общем случае — интерфейса "всего что только есть".
Re[11]: как сократить количество vtbl
От: Erop Россия  
Дата: 26.04.17 17:19
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Чисто субъективно, мне не нравятся операторы приведения, я бы сделал функции типа asSerializable() или querySerializable()

Q>для явного запроса.

Ну это вопрос вкуса, если не вкуса, то можно сделать где-то в каком-то IUnknown шаблонный NVI метод
struct IBase {
    virtual ~IBase(){}
    template<typename I> I* GetAs(  I*& dst ) { return dst = dynamic_cast<I*>( this ); }
    template<typename I> const I* GetAs(  const I*& dst ) const { return dst = dynamic_cast<const I*>( this ); }
}


а в ISuperBar определить нешаблонные виртуальные перегрузки GetAs.
Кроме того, можно определить шаблонный метод GetAs без параметров
    template<typename I> I* GetAs() {
        I* i = 0;
        return this->GetAs( i );
    }
    template<typename I> const I* GetAs() const {
        const I* i = 0;
        return this->GetAs( i );
    }


Тогда всё будет каститься единообразно.

Q>Но суть моих сомнений в другом.

Q>Все хорошо получается, когда рассматриваем один класс CBar и жирный интерфейс ISuperBar.
Q>Попробуем распространить идею на два класса CCat и СDog.
Q>Оба должны быть поддерживать (каким-то способом) ISerializable и полиморфно обрабатываться в foo(ISerializable*).

Q>Итак, как нам объявить ISerializable?


Ты же говорил, что у тебя только одна популярная версия MDT?


Q>Так:

Q>
Q>////////////////////////////
Q>// Serializable.h

Q>#include <SuperCat.h>
Q>struct ISerializable : ISuperCat {
Q>using ISuperCat::Serialize;
Q>};
Q>

Q>Или так:
Q>
Q>////////////////////////////
Q>// Serializable.h

Q>#include <SuperDog.h>
Q>struct ISerializable : ISuperDog {
Q>using ISuperDog::Serialize;
Q>};
Q>


Q>Выход я вижу только в объявлении интерфейса собакота.,

Нет, выход в том, что бы найти популярную пачку интерфейсов, скажем IGentlemansSet, которая покрывает тот MDT, которых у тебя много
И выводишь из IGentlemansSet все нужные интерфейсы, а остальные оставляешь как есть.

В IGentlemansSet нет никакого смысла, это просто хак, что бы склеить указатели на таблицу виртуальных функций популярных интерфейсов.
Это ПОДРОБНОСТЬ РЕАЛИЗАЦИИ, семантика объекта состоит в том, что он ISerializable реализует и ещё что-то, а не какие-то другие хитрости.

Q>А в общем случае — интерфейса "всего что только есть".

Это тоже можно, но, скорее всего не особо выгодно, так как раздует все таблицы виртуальных функций. Я бы затолкал в IGentlemansSet только ПОПУЛЯРНЫЕ интерфейсы (те, vtbl_ptr которых имеет много экземпляров)
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[12]: как сократить количество vtbl
От: qaz77  
Дата: 27.04.17 14:43
Оценка:
Здравствуйте, Erop, Вы писали:

E>Ты же говорил, что у тебя только одна популярная версия MDT?


Я в начале говорил, что много объектов одного класса, реализующего ISerializable и проч.
В этом смысле — да, одна популярная версия.

Далее в ветке упоминалось, что есть 100+ других классов, которые также реализуют ISerializable.
Т.е. одна из баз (по крайней мере) не ассоциируется исключительно с многочисленным классом.
Поэтому хочется, по возможности, ограничить оптимизацию так, чтобы остальные классы затрагивались по минимуму.

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

E>Нет, выход в том, что бы найти популярную пачку интерфейсов, скажем IGentlemansSet, которая покрывает тот MDT, которых у тебя много

E>И выводишь из IGentlemansSet все нужные интерфейсы, а остальные оставляешь как есть.

По смыслу, я так и сделал.
Широко используемые интерфейсы, типа ISerializable, я объединил в такой IGentlemansSet, в паре классов добавил недостающую реализацию.
Далее вывел по цепочке из IGentlemansSet сначала клиентские интерфейсы многочисленного CBar, а затем, во внутренних заголовках,
интерфейсы уровня реализации.

В public/common.h
struct IGentlemansSet { ... };


В public/bar.h
#include "common.h"
struct IBarPublic1: IGentlemansSet { ... };
struct IBarPublic2: IBarPublic1 { ... };


В private/bar_impl.h
#include <public/bar.h>
struct IBarPrivate1: IBarPublic2{ ... };
struct IBarPrivate2: IBarPrivate1 { ... };

struct CBar: IBarPrivate2 { ... };
Re[13]: как сократить количество vtbl
От: Erop Россия  
Дата: 28.04.17 20:58
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Далее в ветке упоминалось, что есть 100+ других классов, которые также реализуют ISerializable.

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

Во-вторых, то, что ISerializable защищённо выводится из IGentlemansSet — это просто оптимизация. Подробность реализации.
Хуже то, что QueryInterface надо иначе делать, но можно это дело унифицировать, если надо.

Q>Поэтому хочется, по возможности, ограничить оптимизацию так, чтобы остальные классы затрагивались по минимуму.

Ну будет часть интерфейсов защищённо выведена из IGentlemansSet. Ну и что?

Q>Еще многочисленный MDT реализует пачку интерфейсов, которые используются только внутри библиотеки,

Q>используют типы данных, которых нет в публичных заголовках.
Q>Чтобы сделать супер базу для MDT, надо будет тащить все эти детали реализации в публичные заголовки,
Q>а этого очень не хочется.

Можно вывести из IGentlemansSet IPrivateGentlemansSet и тайные интерфейсы получать таким же образом из него.


Q>По смыслу, я так и сделал.

Q>Широко используемые интерфейсы, типа ISerializable, я объединил в такой IGentlemansSet, в паре классов добавил недостающую реализацию.
Q>Далее вывел по цепочке из IGentlemansSet сначала клиентские интерфейсы многочисленного CBar, а затем, во внутренних заголовках,
Q>интерфейсы уровня реализации.

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

Q>В public/common.h

Я понимаю. как ты сделал. Я тебе это и предлагал, как альтернативу.
Просто мне вариант, когда доступны только нужные методы, а ненужные не доступны, нравится больше.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[14]: как сократить количество vtbl
От: qaz77  
Дата: 02.05.17 08:53
Оценка:
Здравствуйте, Erop, Вы писали:
E>Я понимаю, что ты сделал, но, IMHO, так как ты сделал, хуже, так как интерфейсы из IGentlemansSet больше портятся, так как них появляются унаследованные левые методы. Можно, например, нечаянно, вызвать не то.

Портятся больше, это да, не спорю.
Зато клиентский код вообще не пришлось менять.

С оператором приведения к IBarN* еще есть проблема.
У меня повсеместно работа с интерфейсами через ссылки происходит, а не через указатели.
Т.е. в клиентском коде пришлось бы массово звездочки расставлять, там где сейчас неявное преобразование.
Re[15]: как сократить количество vtbl
От: Erop Россия  
Дата: 02.05.17 16:46
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>С оператором приведения к IBarN* еще есть проблема.

Q>У меня повсеместно работа с интерфейсами через ссылки происходит, а не через указатели.
Q>Т.е. в клиентском коде пришлось бы массово звездочки расставлять, там где сейчас неявное преобразование.

Можно операторы приведения к ссылке на интерфейс сделать, а не к указателю.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[16]: как сократить количество vtbl
От: qaz77  
Дата: 03.05.17 06:37
Оценка:
Здравствуйте, Erop, Вы писали:
E>Можно операторы приведения к ссылке на интерфейс сделать, а не к указателю.

Ты же предлагал так делать:
virtual operator IBar1*() { return 0; }


Для ссылки — исключение кидать, как в dynamic_cast?
Re: как сократить количество vtbl
От: Кодт Россия  
Дата: 03.05.17 11:15
Оценка: 1 (1)
Здравствуйте, qaz77, Вы писали:

Q>Пользователи моей софтины умудрились так размножить мелкие объекты, что довели бедняжку до bad_alloc (32 битная арх.).

Q>Соответственно встала задача оптимизации потребления памяти.

Извините, что влезаю в чатик не с момента его зарождения — что-то меня тогда отвлекло...

Вот какую идею могу предложить. Вариация на тему NVI.

Если у нас туча долгоживущих мелких объектов (наличие у которых лишних служебных полей стало критично), то мы можем сделать следующее.
Пусть каждый объект умеет отдавать прокси для известных интерфейсов. То есть, у него метаинтерфейс — фактически, dynamic_cast рукодельный
struct IFoo;
struct IBar;
struct IBuz;

struct IObject {
  virtual proxy<IFoo> as_foo() = 0;  // proxy<> - на вкус и цвет.
  virtual proxy<IBar> as_bar() = 0;
  virtual proxy<IBuz> as_buz() = 0;
  // ну и какие-то совсем уж общие полезняшки, присущие всем объектам
  virtual ~IObject() {}
  virtual const char* classname() const = 0;
  .....
};

// пример реализации прокси

template<class I> using proxy = shared_ptr<I>; // неэффективно по скорости - тройная косвенность, но как proof of concept сойдёт

template<class T> struct impl_proxy_IFoo : IFoo {
  shared_ptr<T> obj_;
  impl_proxy_IFoo(shared_ptr<T> obj) : obj_(obj) {}

  void foo1() override { return obj_->foo1(); } // где у T эти функции даже не обязательно виртуальные
  void foo2() override { return obj_->foo2(); } // и даже необязательно совпадающие по именам (хотя это было бы проще)
};

template<class Final> struct impl_IObject {  // один большой CRTP, или россыпью миксинов, или вообще тупо на макросах в каждом классе...
  shared_ptr<IFoo> as_foo() override {
    static_if( support_foo<Final>::value )
      return make_shared<impl_proxy_IFoo<Final>>(static_cast<Final*>(this)->shared_from_this());
    else
      return shared_ptr<IFoo>();
  }
  . . .
};

// использование
shared_ptr<IObject> x = get_some_object_from_big_cloud();
shared_ptr<IFoo> f = x->as_foo();
f->foo1();
f->foo2();

Тут идея в том, что объектов много, а рукояток к ним в каждый конкретный момент времени — мало. Поэтому мы можем раскошелиться.
Причём рукоятки-прокси могут быть даже вообще кортежами функций. (Это ещё более лютый proof of concept, просто чтоб показать идею).
struct IFoo {
  function<void()> foo1(); // или какой-то более легковесный контейнер для замыкания известной природы: T* ->* (T::*)(blablabla)
  function<void()> foo2();
};

template<class I> using proxy<I> = I;

.....
  IFoo as_foo() override {
    return {
      [this](){this->foo1();},
      [this](){this->foo2();},
    };
  }
.....
Перекуём баги на фичи!
Re[2]: как сократить количество vtbl
От: qaz77  
Дата: 03.05.17 14:52
Оценка:
Здравствуйте, Кодт, Вы писали:
К>Вот какую идею могу предложить. Вариация на тему NVI.

shared_ptr<IFoo> f = x->as_foo();


Основная проблема с таким подходом, что в клиентском коде надо менять неявные преобразования на явные вызовы as_foo() и т.п.

Такой пример:
struct IBar: ISerializable,
             IClonable,
             ...
{
};


В клиентском коде:
IBar& bar = findSomeBar(key);
saveToDisk("bar.dat", bar); // тип второго параметра: const ISerializable&


Предлагаемое решение потребует следующей переделки клиентского кода наподобие:
IBar& bar = findSomeBar(key);
shared_ptr<ISerializable> s = bar.as_serializable();
assert(s);
saveToDisk("bar.dat", *s);


Я не написал об этом явно, но мои многочисленные интерфейсы, выставляемые клиенту, не управляют временем жизни объектов.
У всех таких интерфейсов protected non-virtual деструктор.
Объекты живут в большом DOM'е и его внутренняя логика определяет время жизни.
Поэтому в proxy типа shared_ptr нет необходимости.

Без прокси решение вырождается в то, что предлагал Erop:
virtual operator IBar1*() { return 0; }
virtual operator IBar2*() { return 0; }

Т.е. отдачу сырого указателя + возможность неявного преобразования.
Если бы у меня функции типа saveToDisk принимали указатели, а не ссылки, то был бы удобный вариант.
Отредактировано 03.05.2017 14:53 qaz77 . Предыдущая версия .
Re[3]: как сократить количество vtbl
От: Кодт Россия  
Дата: 03.05.17 16:46
Оценка: 1 (1)
Здравствуйте, qaz77, Вы писали:

Q>
Q>shared_ptr<IFoo> f = x->as_foo();
Q>


Q>Основная проблема с таким подходом, что в клиентском коде надо менять неявные преобразования на явные вызовы as_foo() и т.п.


Можно всё обвешать пользовательскими кастами и проксями с семантикой ссылки.
Я писал эти dynamic_cast'ы явно, чтобы их видно было.

Q>Если бы у меня функции типа saveToDisk принимали указатели, а не ссылки, то был бы удобный вариант.


Опять же концепт
http://ideone.com/5XKkGe
#include <iostream>
#include <memory>
using namespace std;

// наши интерфейсы...
struct IObject;

struct IFoo {
    struct Ref; // ссылкообразный тип на данный интерфейс
    virtual operator IObject&() = 0; // возвращение к первоисточнику
    virtual void f() = 0;
    virtual void g() = 0;
};
struct IBar {
    struct Ref;
    virtual operator IObject&() = 0;
    virtual void b() = 0;
    virtual void c() = 0;
};

// ссылки - это фасады пимплов
struct IFoo::Ref {
    template<class T> struct Thunk; // а это - реализация пимпла
    shared_ptr<IFoo> ptr; // лень следить за копиями/ссылками; хочешь, возьми unique_ptr
    operator IObject&() { return *ptr; }
    void f() { ptr->f(); }
    void g() { ptr->g(); }
};
struct IBar::Ref {
    template<class T> struct Thunk;
    shared_ptr<IBar> ptr;
    operator IObject&() { return *ptr; }
    void b() { ptr->b(); }
    void c() { ptr->c(); }
};

// реализации пимплов
template<class T> struct IFoo::Ref::Thunk : IFoo {
    T* ptr; // как и просил - никакого контроля за владением (а может, всё же, хотя бы weak_ptr?)
    Thunk(T* p) : ptr(p) {}
    operator IObject&() { return *ptr; }
    void f() override { ptr->f(); }
    void g() override { ptr->g(); }
};
template<class T> struct IBar::Ref::Thunk : IBar {
    T* ptr;
    Thunk(T* p) : ptr(p) {}
    operator IObject&() { return *ptr; }
    void b() override { ptr->b(); }
    void c() override { ptr->c(); }
};

// порождающая функция проксей
template<class I, class T> // I явный, T выводится
typename I::Ref make_proxy(T* p) {
    return typename I::Ref{
        make_shared<typename I::Ref::template Thunk<T>>(p)
    };
}

// метаинтерфейс (должен быть объявлен после фасадов, ибо возвращает значения)
struct IObject {
    virtual operator IFoo::Ref() = 0;
    virtual operator IBar::Ref() = 0;
};

// вот наш класс!
struct Something : IObject {
    operator IFoo::Ref() override { return make_proxy<IFoo>(this); }
    operator IBar::Ref() override { return make_proxy<IBar>(this); }
    void f() { cout << "Something::f\n"; }
    void g() { cout << "Something::g\n"; }
    void b() { cout << "Something::b\n"; }
    void c() { cout << "Something::c\n"; }
};

// вот наш объект (один штучка)
Something smth;
IObject* lookup() { return &smth; }

// вот наш клиентский код!
void doit(IFoo::Ref foo) {
    foo.f();
    foo.g();
    IBar::Ref bar = (IObject&)foo; // поскольку IFoo и IBar независимы, делаем up-down-каст
    bar.b();
    bar.c();
}

int main() {
    doit(*lookup());
}
Перекуём баги на фичи!
Re[17]: как сократить количество vtbl
От: Erop Россия  
Дата: 03.05.17 19:05
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Ты же предлагал так делать:

Q>
virtual operator IBar1*() { return 0; }


Ну как назвать виртуальные методы -- дело десятое.
А потом можно в NVI методе возвращать сылку
virtual ISerializable* getSerializable() { return 0; }
operator ISerializable&() { return *getSerializable(); } // добавить обработку ошибок по вкусу


На самом деле можно пойти ещё дальше и сделать что-то вроде
template<typename I> bool QI( T*& dst ) { return ( dst = dynamic_cat<T*>( this ) ) != 0; }
virtual bool QI( ISerializable*& dst ) { return ( dst = 0 ) != 0; }
virtual bool QI( IClonable*& dst ) { return ( dst = 0 ) != 0; }
//...
template<typename I> operator I&()
{
    I* dst = 0;
    if( !this->QI( dst ) ) {
        // тут обрабатываем ошибку как-то.
    }
    return *dst;
}


Q>Для ссылки — исключение кидать, как в dynamic_cast?

А у тебя сейчас как сделано? Вот есть у меня ISerializable&, я хочу получить из него IClonable& как получить?
И как узнать, можно или нельзя получать?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[18]: как сократить количество vtbl
От: qaz77  
Дата: 04.05.17 08:25
Оценка:
Здравствуйте, Erop, Вы писали:
E>А у тебя сейчас как сделано? Вот есть у меня ISerializable&, я хочу получить из него IClonable& как получить?
E>И как узнать, можно или нельзя получать?

Получать IClonable& из ISerializable& задача не стоит.
Т.е. у меня нет логики QueryInterface во время выполнения.
Все касты неявные и во время компиляции.

struct IBar: ISerializable,
             IClonable,
             ...
{
};

struct IBaz: IClonable,
         IComparable,
             ...
{
};

IBar& getRandomBar();
IBaz& getRandomBaz();

void saveSomethingToDisk(const char* filename, ISerializable& s);

int main()
{
  IBar& bar = getRandomBar();
  saveSomethingToDisk("bar.dat", bar);

  IBaz& baz = getRandomBaz();
  saveSomethingToDisk("baz.dat", baz); // << compile time error, IBaz не поддерживает ISerializable
  return 0;
}
Re[4]: как сократить количество vtbl
От: qaz77  
Дата: 04.05.17 09:10
Оценка:
Здравствуйте, Кодт, Вы писали:

К>
К>// вот наш клиентский код!
К>void doit(IFoo::Ref foo) {
К>    foo.f();
К>    foo.g();
К>    IBar::Ref bar = (IObject&)foo; // поскольку IFoo и IBar независимы, делаем up-down-каст
К>    bar.b();
К>    bar.c();
К>}
К>


Т.е. сигнатуру клиентской функции c doit(IFoo& foo) надо поменять на doit(IFoo::Ref foo).
Клиентского кода много и не хотелось бы его трогать вообще.

И не стоит задача кастов между независимыми IFoo и IBar.
Только неявные касты от производного к базе с проверкой во время компиляции.

Т.к. Something не реализует напрямую IFoo и IBar, то приходится создавать временные объекты Thunk
с реализацией виртуальных функций. И мы получаем оверхед во время выполнения, где его раньше не было.

Что мне нравится в данном подходе, то что нет жирного интерфейса.
В IObject собраны только фабрики прокси.
IFoo и IBar по сравнению с оригинальными интерфейсами добавилось только приведение к IObject&.

Для многочисленных классов, которые не оптимизируем по кол-ву vptr, как реализовывать operator IObject&?
Т.е. такой например:
struct OldBig: IFoo
{
  // реализация IFoo
  virtual void f() override;
  virtual void g() override;

  // теперь еще
  virtual operator IObject&() override { /*запретить бы его вызов в compile time*/ } 
};
Re[5]: как сократить количество vtbl
От: Кодт Россия  
Дата: 04.05.17 13:15
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Т.е. сигнатуру клиентской функции c doit(IFoo& foo) надо поменять на doit(IFoo::Ref foo).


Ну или
void doit(IFoo& foo);
...
doit((IFoo::Ref)*lookup());
...


Q>Клиентского кода много и не хотелось бы его трогать вообще.


Ну, какое-то количество кода придётся потрогать в любом случае.

Q>И не стоит задача кастов между независимыми IFoo и IBar.

Q>Только неявные касты от производного к базе с проверкой во время компиляции.

Это я сделал в подарок, практически нахаляву. Если не нужно, то просто отломи каст к IObject& у частных интерфейсов.

Q>Т.к. Something не реализует напрямую IFoo и IBar, то приходится создавать временные объекты Thunk

Q>с реализацией виртуальных функций. И мы получаем оверхед во время выполнения, где его раньше не было.

Да, это компромисс пространство-время. Либо у нас маленькие объекты с медленным доступом (платим косвенностью), либо большие (платим указателями) с быстрым.

Q>Для многочисленных классов, которые не оптимизируем по кол-ву vptr, как реализовывать operator IObject&?

Q>Т.е. такой например:
Q>
Q>struct OldBig: IFoo
Q>{
Q>  // реализация IFoo
Q>  virtual void f() override;
Q>  virtual void g() override;

Q>  // теперь еще
Q>  virtual operator IObject&() override { /*запретить бы его вызов в compile time*/ } 
Q>};
Q>


В конце концов, можно все интерфейсы унаследовать от IObject, и тогда проблема отпадёт автоматически!

-----
Кстати, ещё один вариант что-то надумался.

1. Послушаем совета — сделаем мега-интерфейс IEverything (унаследованный от всех частных — и с тучей vptr'ов).
2. Сделаем у объекта фабрику, отдающую прокси этого типа. Почти так же, как IFoo::Ref::Thunk, только нам теперь не нужен Ref.
3. Будет ли эта фабрика виртуальным членом классов объектов предметной области, или извне нахлобучиваться, скажем, в функциях поиска — это обсуждабельно
unique_ptr<IEverything> lookup() {
  ..... make_everything( the_object ) .....
}
Перекуём баги на фичи!
Re[6]: как сократить количество vtbl
От: qaz77  
Дата: 05.05.17 06:33
Оценка: +2
В случае, как я сделал (http://rsdn.org/forum/cpp/6768973.1
Автор: qaz77
Дата: 27.04.17
) нет ряда недостатков.

Здравствуйте, Кодт, Вы писали:
К>Ну, какое-то количество кода придётся потрогать в любом случае.
Клиентский код вообще не трогал.

К>Да, это компромисс пространство-время. Либо у нас маленькие объекты с медленным доступом (платим косвенностью), либо большие (платим указателями) с быстрым.

Ничем не пожертвовал. Выиграл память, но не проиграл скорость.
Может даже копеечку выиграл, т.к. при нескольких vptr каст добавляет смещение базы, а при одном — ноль.

Компромисс в другом. Размер объекта или внутренняя стройность дизайна.
Пришлось пожертвовать логикой при выведении независимых интерфейсов друг из друга.
Появился артефакт в виде возможности неявного приведения IBar к IFoo, чего раньше не было.
Старый клиентский код это не ломает, но при написании нового может озадачить.

Все же я считаю, что свойства ощущаемые потребителем, такие как выделенная память и скорость,
важнее внутренней кухни, с которой имеют дело только программисты.
Re[19]: как сократить количество vtbl
От: Erop Россия  
Дата: 05.05.17 18:24
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Все касты неявные и во время компиляции.


Ну, тогда, можно операторы преобразования сделать защищёнными и открывать их, вместо того, что бы реализовывать.

Ну, то есть, если IBar не играет в оптимизацию, то пишешь так:

Q>
Q>struct IBar: ISerializable,
Q>             IClonable,
Q>             ...
Q>{
Q>};

а если играет, то так:
struct IBar : protected IGentlemansSet {
    using IGentlemansSet::operator ISerializable&;
    using IGentlemansSet::operator IClonable&;
        ...
};

и БОЛЬШЕ НИЧЕГО НЕ МЕНЯЕШЬ
Q>
Q>struct IBaz: IClonable,
Q>         IComparable,
Q>             ...
Q>{
Q>};

Q>IBar& getRandomBar();
Q>IBaz& getRandomBaz();

Q>void saveSomethingToDisk(const char* filename, ISerializable& s);

Q>int main()
Q>{
Q>  IBar& bar = getRandomBar();
Q>  saveSomethingToDisk("bar.dat", bar);

Q>  IBaz& baz = getRandomBaz();
Q>  saveSomethingToDisk("baz.dat", baz); // << compile time error, IBaz не поддерживает ISerializable
Q>  return 0;
Q>}
Q>




p.s.
Мало того, в самом IGentlemansSet тоже минимальный объём кода и никаких RT накладных расходов:
// Предъобявления интиерфейсов
struct ISerializable;
struct IClonable
// ...

struct IGentlemansSet {
protected:
    virtual ~IGentlemansSet() {}

    template<typename T> T* to();
    template<typename T> const T* to() const; // если надо
    
    operator ISerializable& () { return *to<ISerializable>(); }
    operator IClonable&() { return *to<IClonable>(); }
    // ...

    // virtual методы ISerializable
    virtual void save( TSaveDst& ) const { assert( false ); }
    virtual bool load( TLoadSrc& ) { assert( false ); return false; }
    
    // virtual методы IClonable
    // ...

    // ...

};

template<typename T>
T* IGentlemansSet::to<T>() { return static_cast<T*>( this ); }

template<typename T>
const T* IGentlemansSet::to<T>() const { return static_cast<T*>( this ); }
тут, правда, есть подводный камень, который состоит в том, что определения типов аргументов и результатов методов из всех интерфейсов джентльменского набора придётся включить или предопределить, хотя бы. Но может это и не создаст много зависимостей.
В любом случае это не дороже наследования левых интерфейсов друг от друга.
И да, напоминаю, что типичный хедер оптимизированного интерфейса будет выглядеть теперь так:
// было
struct ISerializable {
    virtual void save( TSaveDst& ) const = 0;
    virtual bool load( TLoadSrc& ) = 0;
};

// стало
struct ISerializable : protected IGentlemansSet {
    using IGentlemansSet::load;
    using IGentlemansSet::save;
}


С точки зрения пользователя библы ничего вообще не поменяется.
С точки зрения того, кто реализует неоптимизированные интерфейсы, очень мало
И только в тех классах, которые играют в оптимизацию, что-то поменяется. Но они все твои, как я понял, и их немного?

Я вижу три проблемы:
1) Нелегальный down cast. IMHO эта проблема носит чисто формальный характер. В этом смысле такое решение чуть хуже решения с выведением всех интерфейсов IGentlemansSet друг из друга.
2) В IGentlemansSet появляется лишняя связность кода. Но тут с вариантом с выведением паритет, или, даже, чуть лучше, так как типы нужные в ISerializable, в GentlemansSet.h можно только объявить, а давать им определение только в Serializable.h
3) Нет CT проверки, что в MDT реализованы все нужные методы, так как мы не можем теперь методы IGentlemansSet сделать pure virtual. Тут с "наследственной" схемой тоже паритет, но тут можно немного побороться.
Можно так реструктурировать это код, что в зависимости от ключа компиляции/макроса будет собираться обычная или оптимизированная версия, и все проверки делать в обычной.

  таки прикручиваются CT проверки реализации нужных методов!
собственно было у тебя, как я понимаю, так:
  было
// Serializable.h

class CSaveDst {
    // тут определение
};

class CLoadSrc {
    // тут определение
};

struct ISerializable {
    virtual void Save(CSaveDst&) const = 0;
    virtual bool Load(CLoadSrc&) = 0;
};

// Printable.h
struct IPrintable {
    virtual void Print() const = 0;
};

// FreqUsed.h
struct IFreqUserBar : IPrintable, ISerializable {
};


struct IFreqUsedBaz : IPrintable {
};

// FreqUsed.inc
struct CFreqUsedBar : IFreqUsedBar {

    // ISerializable
    void Save(CSaveDst&) const override { };
    bool Load(CLoadSrc&) override { return false;  }

    // IPrintable
    void Print() const override { } // ***
};

struct CFreqUsedBaz : IFreqUsedBaz {
    // IPrintable
    void Print() const override {  }
};

// UsualUsed.h
struct IBar : IPrintable, ISerializable {

};

struct IBaz : IPrintable {

};

// UsualUsed.inc
struct CBar : IBar {
    // ISerializable
    void Save(CSaveDst&) const override { };
    bool Load(CLoadSrc&) override { return false; }

    // IPrintable
    void Print() const override {  }
};

struct CBaz : IBaz {
    // IPrintable
    void Print() const override {  }
};

// Test.cpp
void saveIt(ISerializable&);

void test(IFreqUsedBar& fr, IFreqUsedBaz& fz, IBar& r, IBaz& z)
{
    CFreqUsedBar bar;  // если закомментировать строчку ***, то не соберётся
    saveIt(fr);
//    saveIt(fz);        // не соберётся, так как IFreqUsedBaz не умеет ISerializable
    saveIt(r);
//    saveIt(z);         // не соберётся, так как IBaz не умеет ISerializable
}

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

  магия на макросах
// GentlemansSet.h
// Управляет тем, оптимизированную или проверочную версию мы собираем.
#define _USE_GENTLEMANS_SET_OPTIMIZATION

#ifdef _USE_GENTLEMANS_SET_OPTIMIZATION
#define GENTLEMANS_SET_OPTIMIZATION_BASE(...) private IGentlemansSet

#define GENTLEMANS_SET_OPTIMIZATION_ENABLE_( I ) \
    using IGentlemansSet::operator I&;

#define GENTLEMANS_SET_OPTIMIZATION_ENABLE( I ) \
    GENTLEMANS_SET_OPTIMIZATION_ENABLE_( I )\
    GENTLEMANS_SET_OPTIMIZATION_ENABLE_( const I )

#define GENTLEMANS_SET_OPTIMIZATION_OVERRIDE_ override = 0

#else// _USE_GENTLEMANS_SET_OPTIMIZATION
#define GENTLEMANS_SET_OPTIMIZATION_BASE(...) __VA_ARGS__
#define GENTLEMANS_SET_OPTIMIZATION_ENABLE( I ) 
#define GENTLEMANS_SET_OPTIMIZATION_INTERFACE_IMP_( I )
#define GENTLEMANS_SET_OPTIMIZATION_OVERRIDE_ = 0

#endif// _USE_GENTLEMANS_SET_OPTIMIZATION

struct ISerializable;
    class CSaveDst;
    class CLoadSrc;

struct IPrintable;
// ...

struct IGentlemansSet {
protected:
    virtual ~IGentlemansSet() {}

#    ifdef _USE_GENTLEMANS_SET_OPTIMIZATION
    #define GENTLEMANS_SET_OPTIMIZATION_INTERFACE_BEGIN_( I )\
        operator I&();\
        operator const I&() const;

    #define GENTLEMANS_SET_OPTIMIZATION_INTERFACE_END_()
    #define GENTLEMANS_SET_OPTIMIZATION_INTERFACE_IMP_( I )\
        inline IGentlemansSet::operator I&() { return *static_cast<I*>(this); }\
        inline IGentlemansSet::operator const I&() const { return *static_cast<const I*>(this); }

    GENTLEMANS_SET_OPTIMIZATION_INTERFACE_BEGIN_(ISerializable)
        virtual void Save(CSaveDst&) const { assert(false); }
        virtual bool Load(CLoadSrc&) { assert(false); return false; }
    GENTLEMANS_SET_OPTIMIZATION_INTERFACE_END_()

    GENTLEMANS_SET_OPTIMIZATION_INTERFACE_BEGIN_(IPrintable)
        virtual void Print() const { assert(false); }
    GENTLEMANS_SET_OPTIMIZATION_INTERFACE_END_()

    // ...
#    endif// _USE_GENTLEMANS_SET_OPTIMIZATION
};

// Serializable.h
//#include <GentlemansSet.h>

class CSaveDst {
    // тут определение
};

class CLoadSrc {
    // тут определение
};

struct ISerializable : private IGentlemansSet { friend IGentlemansSet;
    virtual void Save(CSaveDst&) const GENTLEMANS_SET_OPTIMIZATION_OVERRIDE_;
    virtual bool Load(CLoadSrc&) GENTLEMANS_SET_OPTIMIZATION_OVERRIDE_;
    
};
GENTLEMANS_SET_OPTIMIZATION_INTERFACE_IMP_(ISerializable)

// Printable.h
//#include <GentlemansSet.h>

struct IPrintable : private IGentlemansSet { friend IGentlemansSet;
    virtual void Print() const GENTLEMANS_SET_OPTIMIZATION_OVERRIDE_;
};
GENTLEMANS_SET_OPTIMIZATION_INTERFACE_IMP_(IPrintable)

// FreqUsed.h
struct IFreqUsedBar : GENTLEMANS_SET_OPTIMIZATION_BASE(IPrintable, ISerializable) {
    GENTLEMANS_SET_OPTIMIZATION_ENABLE(ISerializable);
    GENTLEMANS_SET_OPTIMIZATION_ENABLE(IPrintable);
};

struct IFreqUsedBaz : GENTLEMANS_SET_OPTIMIZATION_BASE(IPrintable) {
    GENTLEMANS_SET_OPTIMIZATION_ENABLE(IPrintable);
};

// FreqUsed.inc
struct CFreqUsedBar : IFreqUsedBar {
    // ISerializable
    void Save(CSaveDst&) const override { };
    bool Load(CLoadSrc&) override { return false;  }

    // IPrintable
    void Print() const override { }  // ***
};

struct CFreqUsedBaz : IFreqUsedBaz {
    // IPrintable
    void Print() const override {  }
};

// UsualUsed.h
struct IBar : IPrintable, ISerializable {

};

struct IBaz : IPrintable {

};

// UsualUsed.inc
struct CBar : IBar {
    // ISerializable
    void Save(CSaveDst&) const override { };
    bool Load(CLoadSrc&) override { return false; }

    // IPrintable
    void Print() const override {  }
};

struct CBaz : IBaz {
    // IPrintable
    void Print() const override {  }
};

// Test.cpp

void saveIt(ISerializable&);

void test(IFreqUsedBar& fr, IFreqUsedBaz& fz, IBar& r, IBaz& z)
{
    CFreqUsedBar bar;  // если закомментировать строчку ***, то не соберётся
    saveIt(fr);
//    saveIt(fz);        // не соберётся, так как IFreqUsedBaz не умеет ISerializable
    saveIt(r);
//    saveIt(z);         // не соберётся, так как IBaz не умеет ISerializable
}

Да, обрати внимание, что как-то меняется только тот код, который нуждается в оптимизации. И твой код, который в оптимизации не нуждается, и все реализации (это FreqUsed.inc, UsualUsed.h, UsualUsed.inc Test.cpp) переехал из версии "было" БЕЗ ПРАВОК

Ну и левых связностей не возникло. Все наследования из IGentlemansSet приватные, явно он нигде не фигурирует и т. д

Думаю, что на той же идее специальной тестовой сборки без оптимизации, но с проверками, можно и более шаблонное решение сделать, что бы вместо
struct IFreqUsedBar : GENTLEMANS_SET_OPTIMIZATION_BASE(IPrintable, ISerializable) {
    GENTLEMANS_SET_OPTIMIZATION_ENABLE(ISerializable);
    GENTLEMANS_SET_OPTIMIZATION_ENABLE(IPrintable);
};
можно было писать что-то вроде
struct IFreqUsedBar : IGentlemansSet::TBase<IPrintable, ISerializable> {
};

Ща подумаю...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Отредактировано 05.05.2017 22:25 Erop . Предыдущая версия . Еще …
Отредактировано 05.05.2017 19:44 Erop . Предыдущая версия .
Отредактировано 05.05.2017 19:43 Erop . Предыдущая версия .
Отредактировано 05.05.2017 18:56 Erop . Предыдущая версия .
Re[20]: как сократить количество vtbl
От: qaz77  
Дата: 10.05.17 07:53
Оценка:
Здравствуйте, Erop, Вы писали:

E>Я вижу три проблемы:

E>1) Нелегальный down cast. IMHO эта проблема носит чисто формальный характер. В этом смысле такое решение чуть хуже решения с выведением всех интерфейсов IGentlemansSet друг из друга.
E>2) В IGentlemansSet появляется лишняя связность кода. Но тут с вариантом с выведением паритет, или, даже, чуть лучше, так как типы нужные в ISerializable, в GentlemansSet.h можно только объявить, а давать им определение только в Serializable.h
E>3) Нет CT проверки, что в MDT реализованы все нужные методы, так как мы не можем теперь методы IGentlemansSet сделать pure virtual. Тут с "наследственной" схемой тоже паритет, но тут можно немного побороться.
E>Можно так реструктурировать это код, что в зависимости от ключа компиляции/макроса будет собираться обычная или оптимизированная версия, и все проверки делать в обычной.

E>таки прикручиваются CT проверки реализации нужных методов!


Получилось довольно проработанное решение.
Только с двумя версиями сборки — громоздко.
Re[21]: как сократить количество vtbl
От: Erop Россия  
Дата: 10.05.17 12:06
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Только с двумя версиями сборки — громоздко.


Это всего лишь скрипт билдовый.
Текущее твоё решение вообще не проверяет, что нужные функции определены вроде как.
Или у тебя есть упорядочение интерфейсов, которое позволяет всегда выводиться только из актуальных?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[22]: как сократить количество vtbl
От: qaz77  
Дата: 11.05.17 14:24
Оценка:
Здравствуйте, Erop, Вы писали:
E>Текущее твоё решение вообще не проверяет, что нужные функции определены вроде как.
E>Или у тебя есть упорядочение интерфейсов, которое позволяет всегда выводиться только из актуальных?

У меня сейчас нет фейковых реализаций функций с ассертами внутри.
В интерфейсах честные виртуальные функции = 0.

То, что прилетело нового из IGentlemanSet, проще было до-реализовать в классах. Благо, там совсем немного добавилось.

Я понимаю, что для общего случая это не пройдет.
Согласен, что твой вариант более универсальный.
Re[23]: как сократить количество vtbl
От: Erop Россия  
Дата: 12.05.17 11:49
Оценка:
Здравствуйте, qaz77, Вы писали:

Q>Я понимаю, что для общего случая это не пройдет.

Q>Согласен, что твой вариант более универсальный.

Он ещё лучше защищает пользователя от неправильных кастов к незапланированным интерфейсам
И не затрагивает всех, кто хочет реализовать неоптимизированный интерфейс
Но я согласен, что на месте виднее, что лучше.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Отредактировано 14.05.2017 11:05 Erop . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.