LINUX.ORG.RU

Давайте поговорим о трейтах

 , , , ,


0

2

Вот я смотрю очередной раз на трейты. Первоистчник говорит:

Traits do not specify any state variables, and the methods provided by traits never directly access state variables.

Далее смотрим первую попавшуюся статью по PHP и видим:

trait Id
{
    protected $id;
    public function getId()
    {
        return $this->id;
    }
}

Т.е., в PHP трейты - это не трейты, я верно понял?

И теперь вот что я хотел узнать - как соотносятся по производительности трейты с одиночным наследованием реализации. Тут я слегка упираюсь в то, что я не понимаю, как реализованы вирт. ф-ии в С++ при множественном наследовании. Как я понял, если класс Child наследует от Parent1 и Parent2, и мы хотим вызвать виртуальный метод Parent1::Method, то мы должны где-то найти указатель на этот метод. Метод ищется в VMT и Child содержит 2 VMT, для каждого из Parent.

И получается, чтобы найти этот VMT, нам нужно сначала вызвать некую ф-ю VMTOffsetOfParent1(Child). Эта функция может вызываться для всех потомков Parent1, и может принимать на вход только какое-то число, идентификатор класса, а возвращать она должна указатель. Такое отображение можно реализовать только с помощью хеш-таблицы, b-дерева или иного объекта с логарифмическим временем доступа по отношению к количеству потомков Parent1.

По сравнению с этим, при одиночном наследовании вызов вирт.метода осуществляется за линейное время.

Если Parent1 и Parent2 становятся трейтами, то опять же нам нужно вызвать функцию GetParent(Child), которая опять же требует хеш-таблицы для своей реализации, со временем доступа, логарифмическим по отношению к количеству классов, воплощающих этот трейт.

Вывод отсюда такой: одиночное наследование реализации с таблицей виртуальных методов даёт существенный выигрыш в производительности по сравнению с множественным наследованием и трейтами. Мы можем это по всячески оптимизировать, кешируя найденные трейты или VMT, но это не отменяет исходной разницы.

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

Верно я мыслю или нет? В свете этого, верно ли, с точки зрения производительности, решение отказаться от наследования реализации, принятое в Rust и Golang?

А зачем мне это нужно: нужно быстро придумать, как реализовать ООП в Яре. Понятно, что нужно включить минимум для начала (потому что версия только 0.4). Но любой минимум потом оказывается более, чем минимальным, и включив что-то в язык, потом уже не избавишься. Поэтому тэг «Яр» здесь должен стоять, уж извините. Он особенно хорош тем, что те, кому я надоел, ставят этот тег в список игнорируемых.

★★★★★

Последнее исправление: den73 (всего исправлений: 2)

Ты исходишь из предположения, что реализации всегда таскают с собой VMT, и диспетчеризация всегда динамическая.
Это предположение неверно.

quantum-troll ★★★★★
()
Ответ на: комментарий от i36_zubov

В c++ хитрости начинаются только с виртуальным наследованием.

Непохоже. При простом множественном наследовании уже нужна VMT для каждого из предков, не наследующих друг друга. И для вызова виртуального метода, определённого в не основном предке, нужно сделать следующее:

  • достать VMT этого предка по некоему смещению, известному во время компиляции
  • из указателя на наш объект сделать указатель на этого предка, добавив к нему смещение предка внутри нашего объекта, известное во время компиляции
  • вызвать этот метод над этим предком

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

И вот этот момент я вчера не осознал. Т.е. при множественном наследовании выполнение виртуальных методов всё же дороже, хотя это не зависит от сложности иерархии, а является некоей константой на каждый переход от потомка к предку и обратно.

Т.е. по сути получается, что в С++ при множественном наследовании миксины как бы есть, но они используются неявно. В этом смысле сделать их явными - это вроде как шаг вперёд с точки зрения контроля над производительностью.

den73 ★★★★★
() автор топика
Ответ на: комментарий от quantum-troll

Ты исходишь из предположения, что реализации всегда таскают с собой >VMT, и диспетчеризация всегда динамическая.
Это предположение неверно.

Скор ты на расправу. Я не исхожу из этого предположения, а рассматриваю те случаи, где это имеет место. Со статикой всё понятно.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

не нужна там никакая таблица кроме той, которую ты по факту будешь использовать а она зависит только от типа указателя и искать ее не надо-указатель на таблицу первым элементом в классе идет

i36_zubov
()
Ответ на: комментарий от asaw

Таблица - это одна из реализаций. У неё тоже своя стоимость - место в кеше будет занимать зря.

den73 ★★★★★
() автор топика
Ответ на: комментарий от i36_zubov

В - потомок А и Б. В Б определён вирт. метод м, который не перекрыт в В. У нас есть *В, и мы вызываем В->м(). В качестве self при этом вызове нужно передать в м() не сам *В, а (*Б)((char *)В + смещение_Б_внутри_В).

den73 ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Упс:

«Упс» потому, что когда в диаграмме наследования появляется ромб с общим предком B1 наверху, от него нужно наследоваться виртуально, дабы не появлялось дублирования членов. Вот только не надо мне говорить, что это и есть то самое «большинство случаев».


int main()
{
    struct B1 {
        virtual void foo() = 0;
    };

    struct B2 : virtual B1 {
        virtual void bar() = 0;
    };

    struct C1 : virtual B1 {
        void foo() { ... }
    };

    struct C2 : C1, B2 {
        void bar() { ... }
    };

    C2 c2;
}

asaw ★★★★★
()
Ответ на: комментарий от quantum-troll

Статика не может использоваться, если нужен полиморфизм. Только давай не будем переводить разговор в тему «если тебе нужен полиморфизм, то ты неправ» :) Меня интересует эффективность разных реализаций полиморфизма.

den73 ★★★★★
() автор топика
Ответ на: комментарий от asaw

Это вы, товарищи в дебри полезли. Я лично ещё и с простым множественным наследованием не разобрался, да и не только я, судя по комментам :)

У меня в языке не будет множественного наследования реализации и ромба тоже не будет, поэтому проблемы С++, связанные с ромбом, меня не интересуют.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Статика не может использоваться, если нужен полиморфизм

Полиморфизм бывает разным. Если речь о параметрическом полиморфизме, то может и используется.

Zenom ★★★
()
Ответ на: комментарий от den73

Статика не может использоваться, если нужен полиморфизм.

Может, если полиморфизм — параметрический.

quantum-troll ★★★★★
()
Ответ на: комментарий от den73

У меня в языке не будет множественного наследования реализации и ромба тоже не будет

Если классы абстрактные и без членов, то в множественном, и, в т.ч., ромбовидном наследовании проблемы никакой нет :-) А все эти виртуальные базовые классы «во избежание дублирования данных предков» создают больше проблем, чем решают, поэтому среди ветеранов C++ не пользуются популярностью :-) Это больше инструментик для школьников-теоретиков, начитавшихся примеров с Radio/Transmitter :-)

anonymous
()
Ответ на: комментарий от Zenom

Здесь чудес не бывает.

Параметрический полиморфизм подразумевает исполнение одного и того же >кода для всех допустимых типов аргументов,

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

Мы можем стараться больше применить статику, но если мы совсем уберём динамику, то язык потеряет возможности.

Мы можем её вытеснить со сцены (или даже из сознания), но от этого она не исчезнет. Моя задача не в том, чтобы спрятать динамику и не в том, чтобы её меньше применять, а в том, чтобы её рассмотреть вблизи и понять, как сделать её правильно.

den73 ★★★★★
() автор топика
Ответ на: комментарий от asaw

Количество тем о множественном наследовании как бы намекает.

RazrFalcon ★★★★★
()
Ответ на: комментарий от den73

Но она не исчезнет.

Если нормальный компилятор - исчезнет. Ибо условие будет всегда true и компилятор выкинет другую ветку.

RazrFalcon ★★★★★
()
Ответ на: комментарий от den73

Речь не о чудесах, а о мономорфизации. В том же Rust поиск по vtbl используется только для trait objects. В остальных случаях вызовы мономорфизируются. А поскольку trait objects нужны только для специальных случаев, подавляющее большинство кода никакого поиска по таблице виртуальных функций не задействует вообще.

Zenom ★★★
()
Ответ на: комментарий от RazrFalcon

Если нормальный компилятор - исчезнет. Ибо условие будет всегда true и компилятор выкинет другую ветку.

Де-юре не гарантировано :-) Поэтому в C++-17 добавили constexpr if :-)

anonymous
()
Ответ на: комментарий от RazrFalcon

И тут ты?! Брысь.

Как раз на STL смотрю :-) Классная штука :-)

anonymous
()
Ответ на: комментарий от Zenom

Ты же не говоришь «можно полностью исключить динамику», потому что знаешь, что это сделать нельзя.

Вот меня и интересует не вопрос как вытеснить динамику и сколько это будет стоить, а как эффективно реализовать динамику, причём интересует меня довольно узкий вопрос - не зря ли выбросили одиночное наследование реализации?

Можешь поправить меня, если я неправильно понял? В Хаскель я не полезу, но в С++ мономорфизация - это по сути шаблоны и техника состоит в том, что для любого используемого набора типов в угловых скобках генерируется отдельный экземпляр функции, где всё, что можно, вычислено в статике, в т.ч., по возможности, виртуальные методы найдены и их тела подставлены напрямую, без обращения к VMT в рантайме?

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Ну хорошо, мы ведь можем и с другой стороны подойти:

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

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

Опять же, если проблема в том, что есть только одиночное наследование, а нужно множественное, то ничто не мешает мне включить трейты, миксины, интерфейсы. В чём именно вред от одиночного наследования реализации, из-за которого в нём отказались в Rust и Go?

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 2)
Ответ на: комментарий от den73

Похоже, что для реализации плохо то, что если у нас переменная типа «Предок», то это означает «Предок или его любой Потомок» и мы не можем убрать VMT для вызова Предок::ВиртуальныйМетод.

Но это скорее проблема языка, в котором нет разницы между «В точности Предок» и «Предок или его любой Потомок».

Достаточно добавить в язык возможность определить переменную типа «В точности Предок» - и можно избавиться от VMT во многих случаях.

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от den73

В Хаскель я не полезу, но в С++ мономорфизация - это по сути шаблоны и техника состоит в том, что для любого используемого набора типов в угловых скобках генерируется отдельный экземпляр функции, где всё, что можно, вычислено в статике, в т.ч., по возможности, виртуальные методы найдены и их тела подставлены напрямую, без обращения к VMT в рантайме?

Да.

quantum-troll ★★★★★
()
Ответ на: комментарий от den73

Вот меня и интересует не вопрос как вытеснить динамику и сколько это будет стоить, а как эффективно реализовать динамику, причём интересует меня довольно узкий вопрос - не зря ли выбросили одиночное наследование реализации?

Я не вижу связи эффективности с наличием или отсутствием наследования реализации. То есть возьмём пример Rust и trait objects: для вызова функции нужно взять по соответствующему смещению в таблице указатель и вызвать код, на который он указывает. И возьмём пример гипотетического C++ с одиночным наследованием: для вызова функции нужно взять по соответствующему смещению в таблице указатель и вызвать код, на который он указывает. Реализация одна и та же, независимо от того, поддерживается в языке наследование поведения или нет.

Zenom ★★★
()
Ответ на: комментарий от Zenom

Разница в том, что не нужен trait object, его роль выполняет сам объект. Т.е разница не в самом вызове, а в другом месте.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

А чем trait obejct не «сам объект»? Тем, что это указатель? Так в C++ для задействования vtbl тоже нужно обращаться по ссылке или указателю.

Zenom ★★★
()
Ответ на: комментарий от Zenom

Своя VMT нужна на каждую пару (тип, реализуемый-типом-трейт). А trait object - это пара (объект, одна-VMT), см. мануал.

Почти то же, что множественное наследование в С++, разница лишь в том, что для С++ указатель данных указывает не на весь объект, а на кусок, отвечающий данному предку.

Т.е. в расте нужно как бы создать этот trait object и передавать его. Во множественном наследовании С++ этого не нужно - нужно просто подвинуть указатель на объект, чтобы он указывал на кусок, отвечающий предку. При одиночном наследовании не нужно ни то, ни другое - объект - это и есть trait object.

Т.е. даже если в одиночном наследовании добавить трейты, то всё равно останутся «привелигированные трейты», лежащие по пути одиночного наследования. Для каждого из них trait object совпадает с самим объектом, не считая тега типа, если он вообще есть.

И эти трейты будут более эффективны в двух аспектах: они разделяют общую VMT и не нужно создавать отдельно trait object.

Всё верно?

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

Почти то же, что множественное наследование в С++, разница лишь в том, что для С++ указатель данных указывает не на весь объект, а на кусок, отвечающий данному предку.

В C++ любой объект с виртуальными методами аналогичен trait object'у из Rust, т. к. не может обходится без VMT. Так что в C++ попросту нет возможности использовать «сам объект» (если я правильно понял терминологическую разницу между trait object и «самим объектом»). В Rust нет разделения на виртуальные и обычные методы, тип диспетчеризации выбирается не в зависимости от типа метода, а в зависимости от стиля вызова.

trait T {
  fn name(&self) -> &str;
}

struct Foo {}
struct Bar {}

impl T for Foo {
  fn name(&self) -> &str {
    "Foo"
  }
}

impl T for Bar {
  fn name(&self) -> &str {
    "Bar"
  }
}

fn mono<X: T>(x: &X) {
  println!("mono for {}", x.name())
}

fn poly(x: &T) {
  println!("poly for {}", x.name())
}

fn main() {
  let foo = Foo {};
  let bar = Bar {};
  
  mono(&foo);
  mono(&bar);
  
  poly(&foo);
  poly(&bar);
}

В этом коде функции mono и poly аналогичны друг другу, принимают ссылки на одни и те же объекты, но вызов T::name() в коде функции mono гарантированно мономорфизируется, а вызов в коде функции poly будет использовать таблицу виртуальных методов.

Zenom ★★★
()
Ответ на: комментарий от Zenom

Ну и что? Сложение-то произвести всё равно надо во время выполнения, если в функцию пришёл неизвестный указатель на объект. Другое дело, я не настолько знаю ассемблер и может быть за это вычисление нет дополнительных тактов, смутно припоминаю какие-то команды для адресации с константным смещением. Но это если мы не хотим использовать trait object отдельно от «самого объекта». Если хотим использовать, мы должны его выделить на стеке, скопировать в него указатель на данные и указатель на VMT. Оптимизатор теоретически может от всего этого при удаче избавиться, но изначально это так. В С++ сам объект в себе содержит (по смещениям) trait object-ы для всех предков и их не нужно отдельно создавать.

den73 ★★★★★
() автор топика
Последнее исправление: den73 (всего исправлений: 1)
Ответ на: комментарий от Zenom

но вызов T::name() в коде функции mono гарантированно мономорфизируется,

Вот это и есть выгода от отсутствия наследования. poly для Foo - это нечто вполне определённое. Но ничто не мешало бы в плюсах отличать «в точности этот тип» от «этого типа или наследников», и сделать то же самое. Т.е. это не является аргументом для отказа от одиночного наследования реализации.

Так что в C++ попросту нет возможности использовать «сам объект»

Ну, можно и так сказать, «сам объект» в смысле Rust в С++ отсутствует, если, конечно, его не смоделируем на структурах. Зато если мы хотим в Rust использовать динамику, нам нужно любой «сам объект» преобразовать в трейт и это должен быть отдельный код для каждого типа, который мы хотим преобразовать в динамику. То, что этот код мы можем нагенерировать из шаблонов (или как оно там, мономорфизации), отчасти облегчает нашу участь, но нам всё же нужно создавать trait object, а в C++, если нам изначально и нужен trait object, то он у нас уже есть. Т.е. нельзя сказать, что тут Rust «всухую» обыграл С++ - думаю, всё же можно привести пример, когда за счёт отсутствия нужды в trait object С++ будет быстрее.

Хотя в целом идея не таскать с собой VMT в те места, где она не нужна, и, самое главное, не инстанциировать её вместе с каждым экземпляром объекта, похоже, даёт Rust-у преимущество перед C++ в каких-то случаях.

Но я не вижу, как это вредит идее одиночного наследования реализации.

Одиночное наследование реализации само по себе тоже не требует таскать VMT за каждым экземпляром объекта. Мы можем поступить как и в Расте - у нас Parent и Child вроде как не родственники, мы лишь говорим, что реализация трейта &InterfaceOfParent для Child заимствуется от Parent. При этом почему-то случайно оказывается так, что все поля, общие между Parent и Child, находятся в них на одном и том же месте.

Благодаря совпадению layout-а полей, не нужно генерировать две копии кода для какого-нибудь Zzz<T>, если Zzz ссылается только на &InterfaceOfParent. Код для Zzz<Child> и Zzz<Parent> случайно оказывается идентичным. Меньше кода = профит.

Другое дело, это подходит не для всех трейтов, а только для унаследованных Child-ом от Parent-а, но их может быть много и тогда профит будет велик.

Т.е. я всё же продолжаю недоумевать, чем провинилось наследование реализации и даже начинаю укрепляться в мысли, что его надо включить в Яр.

den73 ★★★★★
() автор топика
Ответ на: комментарий от Zenom

Так что в C++ попросту нет возможности использовать «сам объект» (если я правильно понял терминологическую разницу между trait object и «самим объектом»)

Можно же, но кривенько - через полные имена ф-ий вроде Class::func(...)

mashina ★★★★★
()
Ответ на: комментарий от mashina

Ааа, ну я не знаток С++ - тогда тем более.

den73 ★★★★★
() автор топика
Ответ на: комментарий от CatsCantFly

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

anonymous
()
Ответ на: комментарий от den73

Да, это хороший вариант, в стиле Си. Мне нравится. Я его имел в виду. Но создание более высокоуровневых конструкций для ООП неизбежно. Вопрос лишь в том, стоит ли сдирать с Раста/Го или они ещё сами не поняли, что у них получилось.

За Go не скажу, но в расте так и сделали. Не совсем понимаю какую скорость ты сравниваешь потому что при прямом вызове метода или при использовании дженериков это будет аналогично простому вызову функции. Если же используется «интерфейс», то будет обращение через vtable, которая привязана к конкретному указателю, а не самому объекту.

DarkEld3r ★★★★★
()

И теперь вот что я хотел узнать - как соотносятся по производительности трейты с одиночным наследованием реализации. Тут я слегка упираюсь в то, что я не понимаю, как реализованы вирт. ф-ии в С++ при множественном наследовании. Как я понял, если класс Child наследует от Parent1 и Parent2, и мы хотим вызвать виртуальный метод Parent1::Method, то мы должны где-то найти указатель на этот метод. Метод ищется в VMT и Child содержит 2 VMT, для каждого из Parent.

И получается, чтобы найти этот VMT, нам нужно сначала вызвать некую ф-ю VMTOffsetOfParent1(Child). Эта функция может вызываться для всех потомков Parent1, и может принимать на вход только какое-то число, идентификатор класса, а возвращать она должна указатель.

ЕМНИП, нет. эта функция считается во время компиляции, и VMT Child содержит в себе дополнительно VMT Parent, как примесь (миксин). то есть, ничего дополнительно в рантайме считать не нужно.

в какой-то книге про COM, ATL, WTL было более подробно расписано. ещё в Dylan про реализацию COM (lovesan оттуда слизал себе в DOORS).

тут прикол в том, что миксины, множественное наследование интерфейсов шаблонами в ATL ложится 1:1 на COM модель. то есть, в памяти раскладка экземпляра класса такова, что 1:1 методы становятся указателями на интерфейсы COM.

нужно писать какие-то простенькие примеры, однострочники и смотреть отладчиком gdb и иже с ним раскладку класса, как его реализует тот же GCC.

либо взять хелловорд на ATL COM , скомпилять студией и посмотреть бинарную раскладку класса в памяти под отладчиком.

anonymous
()
Ответ на: комментарий от anonymous

что-то типа такого посмотри

ИМХО, надо взять книжку «ATL и COM» с примерами, и скомпилять хелловорд с шаблоном шаблонов шаблонов — теми самыми миксинами.

и посмотреть эту сгенерированную раскладку класса под отладчиком.

anonymous
()
Ответ на: комментарий от anonymous

например, возьми пример из этой статьи

и откомпиляй его студией. в сервере COM_ATL_Tutorial_Source.zip среди сгенерированной лапши на С++ ты увидишь в First_ATL.h

/////////////////////////////////////////////////////////////////////////////
// CFirst_ATL
class ATL_NO_VTABLE CFirst_ATL : 
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CFirst_ATL, &CLSID_First_ATL>,
	public IFirst_ATL
{
public:
	CFirst_ATL()
	{
	}

DECLARE_REGISTRY_RESOURCEID(IDR_FIRST_ATL)
DECLARE_NOT_AGGREGATABLE(CFirst_ATL)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CFirst_ATL)
	COM_INTERFACE_ENTRY(IFirst_ATL)
END_COM_MAP()

// IFirst_ATL
public:
	STDMETHOD(AddNumbers)(/*[in]*/ long Num1, /*[in]*/ long Num2, /*[out]*/ long *ReturnVal);
};

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

class ATL_NO_VTABLE CFirst_ATL : 
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CFirst_ATL, &CLSID_First_ATL>,
	public IFirst_ATL

в общем, сделай макет. и потыкай его веточкой.

anonymous
()
Ответ на: комментарий от anonymous

ещё с тем же успехом можно потыкать WTL и пример взять не такой, а позабористей:

class CWtlWindow : public CDialogImpl<CWtlWindow>,
		   public CWinDataExchange<CWtlWindow>
{
public:
...
}

«Инда взопрели озимые. Рассупонилось солнышко, расталдыкнуло свои лучи по белу светушку. Понюхал старик Ромуальдыч свою портянку и аж заколдобился…»

anonymous
()
Ответ на: комментарий от anonymous

прикол в том, что в ATL там такая лапша ВСЮ ДОРОГУ, и это не баг — это фича.

лапшекод by design

например, почему нет ATL под MinGW, а только под MSVC? потому что это вообще-то дикий хак — местами завязано на ABI раскладки класса

например, такая милая прелестьгадость как «карта сообщений COM» из сервера COM выше:


BEGIN_COM_MAP(CFirst_ATL)
	COM_INTERFACE_ENTRY(IFirst_ATL)
END_COM_MAP()

это вот по-твоему, что такое? правильно, оно самое: дёргаем за интерфейс через указатель — вызываем метод из миксина через долбаный битхак с указателями, завязанный на ABI.

интересно становится когда таких COM_INTERFACE_ENTRY несколько, и множественные оргазмы наследования от нескольких COM-классов/миксинов

в полученном лапшекоде развёрнутых шаблонов шаблонов шаблонов шаблонов не то что трейты, или миксины, не отыщешь исходный класс с интерфейсами — там сам чОртъ ногу сломит.

а мелкомягким нравится. говорят, инда, заколдобилось. вставляет, значит.

«мощно задвинул. внушаить» (с) Хрюн Моржов из мультика

anonymous
()
Ответ на: комментарий от den73

трейты в целом идея годная. из книги Pfister про КОП, компонентно-ориентированное программирование :

КОП = ООП - наследование реализации

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

наследование — это самый жёсткий тип связи. половина паттернов проектирования — это переизобретение велосипеда «и рыбку съесть, и функцию реализовать» : реализовать наследование поведения через миксин и параметрический полиморфизм.

потому что нужна более гибкая связь.

вот, кстати занятная презентация про «функциональную природу паттернов в ООП»

картинка про FRP с 46 страницы слайдов более естественно ложится на мультиметоды и множественную диспетчеризацию чем на визитор или визитор времени компиляции через шаблоны шаблонов шаблонами через шаблоны.

на самом деле, с функциональщиной сталкиваемся каждый раз — в паттернах проектирования.

anonymous
()
Ответ на: комментарий от anonymous

и пример взять не такой

хотя нет, тут тоже есть над чем позалипать:

template <class T, class TBase = CWindow, class TWinTraits = CControlWinTraits>
class ATL_NO_VTABLE CWindowImpl : public CWindowImplBaseT< TBase, TWinTraits >
{
  public:
	DECLARE_WND_CLASS(NULL)
...

и пообмазываться шаблонами наотличненько.

anonymous
()
Ответ на: комментарий от anonymous

кстати, помедитируй над ATL_NO_VTABLE. и как строится «карта сообщений ATL»/MFC/whatever, в бинарном виде, в ABI раскладки класса в памяти.

anonymous
()

вот вы на С++ программируете — а ведь потом будете этими руками за хлеб хвататься.

anonymous
()
Ответ на: комментарий от den73

а в том, чтобы её рассмотреть вблизи и понять, как сделать её правильно.

деревья диспетчеризации в Nim. в Dylan, ЕМНИП, тоже что-то такое. для мультиметодов.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.