вопервых, уважаемый k.o. ссылавшийся на 3.8 стандарта:
Вы его читали внимательно???
3.8.1:
The lifetime of an object of type T ends when:
— if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
— the storage which the object occupies is reused or released.
выделенное как раз наш случай! как только есть попытка переиспользовать память из-под объекта -- все! считается что его lifetime закончен!
и далее медитируем начиная с 3.8.1 и до упора -- на каждом шагу упоминается про попытки реюзнуть сторыдж из-под класса и как это приводит к UB!
в частности, давате всмотримся в пример кода из 3.8.5:
struct B {
virtual void f();
void mutate();
virtual ˜B();
};
struct D1 : B { void f(); };
struct D2 : B { void f(); };
void B::mutate() {
new (this) D2; // reuses storage – ends the lifetime of *this
f(); // undefined behavior
...
}
немного не такая иерархия классов, но смысл действа аналогичен! ВСЕ! ТОЧКА! UB...
Здравствуйте, c-smile, Вы писали:
А>>>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>>>способ примерно такой:
CS>>>Это стандартная фича языка. Z>>да?? а ссылочку на section в стандарте можно в студию?? %)
CS>А "ссылочку" на что собственно? CS>На то как работает placement new? Или на то что в C++ нет implicit инициализаций полей структур и классов как в Java например. CS>Это все имхо учебников начального уровня требует ибо основы.
что Вы прикидываетесь "дурачком"??
я за язык Вас не тянул утверждать что замена VPTR описанная TC якобы "стандартная фича языка" -- вот ссылочку на эту фичу в стандарте я и хотел увидеть... или Вы, что более вероятно, "обиделись" на поставленный минус... если Ваше настроение улучшится, я легко его могу убрать...
CS>Я знаю use cases где это решение приносит значительный профит.
мне также, как и уважаемому Erop, интересно... послушать про эти самые мистические use case!
Вы, надеюсь, отдаете себе отчет в том, в какой машинный код выливается применение паттерна и в каком месте, появляется мистический "профит" от грязного хака??? судя по тому как Вы настойчиво твердите, складывается ощущение, что Вы применяли подобную "оптимизацию" чуть ли не на прошлой неделе... что за профайлер помог Вам замерить "профит"??? есть ли реальные цыфры или только догадки из головы???
CS>Что есть такое "стандартный (Behaviour) паттерн" в данном контексте?
гуглим про Behaviour паттерны, выбираем по вкусу (из парочки подходящих)
CS>Еще раз: есть области где именно аккуратное и эффективное прописывание всего и вся есть mission critical.
безусловно! но, ТС гдето упомянул что он пишет софт, для запуска спутников, или там управления ядерным реактором?? насколько его задача mission critial? опять догадки? вы ведь не будете отрицать что риск должен быть оправданным?
CS>Я подозреваю что для C++ uses cases это как раз типично. Иначе рекомендовано использовать Java и .NET. Там низкий порог вхождения.
... не надо так расстраиваться из-за минуса... я сейчас уберу... )
Здравствуйте, c-smile, Вы писали:
CS>Здравствуйте, Аноним, Вы писали:
А>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>способ примерно такой:
CS>Это стандартная фича языка.
да?? а ссылочку на section в стандарте можно в студию?? %)
CS>Поэтому хаком это является в том случае если этот момент у тебя не задокументирован должным образом.
это хак в любом случае, в зависимости от того документированный он или нет можно только подставлять разные эпитеты... типа "грязный хак", например
ради чего делать себе жизнь сложнее и тем кому неповезет сопровождать этот код, когда можно легко обойтись "стандартным" (Behaviour) паттерном?
сложнее в том плане, что нужно очень аккуратно контролировать размер объектов, типы полей, заботиться о правильной переинициализации, аккуратно писать конструкторы внутренних классов, и все такое прочее... все это очень жестко завязано на конкрентую задачу и layout классов изменяющих поведение, и шаг вправо\лево -- расстрел памяти... ради чего? какой profit??
CS>>Какая конкретно строка здесь вызывает сомнения?
R>Строка, помеченная жирным, согласно 3.8.1 порождает неопределенное поведение. Об уже этом говорил zaufi здесь
R>Причем, из того, что второй фрагмент well-formed, отнюдь не следует, что первый также well-formed, поскольку никто не гарантирует равенства указателей obj1 и obj2.
Тут дело не в численном равенстве указателей, они могут быть равны, дело в типизации, в том, что компилятор знает, что obj указывает на Base, и может этим знанием пользоватсья, потому что не предполагается, что тип вдруг изменится — это против правил статически типизированного С++. Например, он возьмет адрес из указателя на vtbl при первом обращении и закеширует его в регистр и дальше все вызовы направит непосредственно в нее, минуя указатель на vptr в объекте. Соответственно он изменения указателя на vptr попроcту не заметит.
А когда у нас есть obj2, компилятор либо видит, откуда он пришел, и ставит соответствующий тип статически (девиртуализация), либо, если функция подмены скрыта в бибилотеке, он просто относится к нему как к новому указателю и должен честно пойти в объект и взять указатель на vptr оттуда (после чего он имеет полное право опять его закешировать и т.д.)
Здравствуйте, Аноним, Вы писали:
А>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>способ примерно такой:
Это стандартная фича языка. Поэтому хаком это является в том случае если этот момент у тебя не задокументирован должным образом.
Хаком тут является отсутствие вызова деструктора и отсутствие проверки необходимого размера. Все. Для рассказывающих ужастики повторяю: ВСЕ.
ЗЫ зря ты топик назвал 'замена VPTR' — это создает ощущение жуткого батхерта, которое и стараются передать многочисленные ужасающиеся в этом топике. Надо называть вещи своими именами — placement new на своей "куче".
Как много веселых ребят, и все делают велосипед...
Здравствуйте, zaufi, Вы писали:
Z>Здравствуйте, c-smile, Вы писали:
CS>>Здравствуйте, Аноним, Вы писали:
А>>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>>способ примерно такой:
CS>>Это стандартная фича языка. Z>да?? а ссылочку на section в стандарте можно в студию?? %)
А "ссылочку" на что собственно?
На то как работает placement new? Или на то что в C++ нет implicit инициализаций полей структур и классов как в Java например.
Это все имхо учебников начального уровня требует ибо основы.
CS>>Поэтому хаком это является в том случае если этот момент у тебя не задокументирован должным образом.
Z>это хак в любом случае, в зависимости от того документированный он или нет можно только подставлять разные эпитеты... типа "грязный хак", например
Я знаю use cases где это решение приносит значительный профит.
Z>ради чего делать себе жизнь сложнее и тем кому неповезет сопровождать этот код, когда можно легко обойтись "стандартным" (Behaviour) паттерном?
Что есть такое "стандартный (Behaviour) паттерн" в данном контексте?
Z>сложнее в том плане, что нужно очень аккуратно контролировать размер объектов, типы полей, заботиться о правильной переинициализации, аккуратно писать конструкторы внутренних классов, и все такое прочее... все это очень жестко завязано на конкрентую задачу и layout классов изменяющих поведение, и шаг вправо\лево -- расстрел памяти... ради чего? какой profit??
Детский сад право слово. Не ходите дети в лес — там волки.
Еще раз: есть области где именно аккуратное и эффективное прописывание всего и вся есть mission critical.
Я подозреваю что для C++ uses cases это как раз типично. Иначе рекомендовано использовать Java и .NET. Там низкий порог вхождения.
После уже второго такого обсуждения о хаке виртуальной таблицы стало интересно — а не наткнутся ли любители переписывать таблицы виртуальных методов на грабли в виде девиртуализации проведенной оптимизирующем компилятором?
Знаю, что современные компиляторы в ряде случаев могут использовать т.н. девиртуализацию — прямой вызов функции (или даже inline подстановка) в месте вызова функции.
Уже лень если честно писать proof-of-concept примеры, сериалы ждут.
Кажется, что если такая проблема существует описание классов в стиле C
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, gegMOPO4, Вы писали:
MOP>>Сгенерированный. И унаследовавший виртуальность от предков (хотя бы одного из).
E>Тогда тот предок имеет нетривиальный деструктор и мы получаем, что пункт про виртуальный деструктор избыточен...
ну в С++0х можно явно дефолтные версии генерить...
Я не уверен, но вполне возможно, что можно написать так:
struct A
{
virtual ~A() = default;
};
тут он и виртуальный, и в то же время не определен пользователем
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
KO>>12.4 пункты 3 и 4
KO>>
KO>>A destructor is trivial if it is an implicitly-declared destructor and if:
KO>>— all of the direct base classes of its class have trivial destructors and
KO>>— for all of the non-static data members of its class that are of class type (or array thereof), each such class
KO>>has a trivial destructor.
KO>>Otherwise, the destructor is non-trivial.
R>Ну допустим. Таким образом, пришли к тому, что для использования такого приема требуется чтобы:
Не "допустим", а таково требование стандарта, можно подумать, кто-то умрёт если ты прямо напишешь что ошибся.
R> R>Ни в одном из классов в иерархии наследования не было определенных пользователем деструкторов R>Ни в одном из классов в иерархии наследования не было полей с нетривиальным деструктором R>R>Ну и зачем оно такое счастье?
мы всё ещё обсуждаем вот это?
ТКС>UB. Вообще, хотя бы деструктор предыдущей реализации надо вызвать перед placement new, ну и позаботиться о том, чтобы хватило памяти (sizeof(intf) >= sizeof любой из реализаций) под все реализации. И это все равно не гарантирует отсутствия проблем при оптимизации.
Деструктор, если он тривиальный, вызывать необязательно, UB будет только если размеры или выравние создаваемых объектов не соответсвуют используемой памяти.
Если да, то я нигде не призывал, отказаться от вызова деструкторов, а всего лишь выразил сомнение в том, что обязательно будет UB.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
R>>>Ну допустим. Таким образом, пришли к тому, что для использования такого приема требуется чтобы: KO>>Не "допустим", а таково требование стандарта, можно подумать, кто-то умрёт если ты прямо напишешь что ошибся.
R>Признаю, ошибся.
ТКС>>>UB. Вообще, хотя бы деструктор предыдущей реализации надо вызвать перед placement new, ну и позаботиться о том, чтобы хватило памяти (sizeof(intf) >= sizeof любой из реализаций) под все реализации. И это все равно не гарантирует отсутствия проблем при оптимизации.
KO>>Деструктор, если он тривиальный, вызывать необязательно, UB будет только если размеры или выравние создаваемых объектов не соответсвуют используемой памяти.
KO>>Если да, то я нигде не призывал, отказаться от вызова деструкторов, а всего лишь выразил сомнение в том, что обязательно будет UB.
R>А вот здесь я бы акцент все-таки поставил бы по-другому. UB не будет только в том случае, если есть гарантия того, что ни в одном из классов иерархии нет нетривиальных деструкторов.
Здесь ты тоже немного ошибаешься, UB не будет если "есть гарантия того, что ни в одном из классов иерархии нет нетривиальных деструкторов", или после "смены" vptr мы не вызываем деструктор старого объекта и наша программа не "зависит" (depends что бы это ни значило) от побочных эффектов работы деструктора.
R>Вряд ли такое требование можно назвать типичным для иерархий с абстрактными классами во главе. В исходном сообщении такого ограничения также не накладывается — строчки "//реализация..." говорят о том, что в производных классах могут присутстовать члены с нетривиальными деструкторами. Видимо, Тот-кто-сидит-в-пруду, когда говорил о необходимости вызова деструкторов, также в качестве общего случая рассматривал (вполне обоснованно) ситуацию, когда классы имеют нетривиальные деструкторы.
Строчки "//реализация...", если посмотреть внимательнее, находятся внутри определений метода someFun, поэтому никаких дополнительных членов там нет.
замена VPTR это "грязный" хак?
От:
Аноним
Дата:
03.04.11 20:33
Оценка:
Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их,
способ примерно такой:
class Interface {
public:
virtual void someFun() =0;
};
class Derived1: public Interface {
public:
void someFun() {
//реализация...
}
};
class Derived2: public Interface {
public:
void someFun() {
//реализация...
}
};
class User { // потребительpublic:
Interface intf;
template <class T> changeIntfRealization() {
// возможные проверки на происхождение T от Interface на манер: static_assert(is_base_of<Interface, T>::value, "Ouch");new (&intf)T(); // происходит подмена VPTR
}
};
int main() {
User user;
user.changeIntfRealization<Derived1> ();
user.intf.someFun(); // вызовется реализация Derived1
user.changeIntfRealization<Derived2> ();
user.intf.someFun(); // вызовется реализация Derived2
}
Здравствуйте, c-smile, Вы писали:
CS>Хотел бы я посмотреть на компилятор делающий "девиртуализацию" COM или Corba интерфейсов которые есть сугубо abstract classes. CS>COM и Corba интерфейсы имеют форму понимаемую и C++ и C. Т.е. всегда возможно получить доступ к vtbl полю. Хотя бы зайдя через C.
Ну посмотри на gcc и MSVC, например...
Скажем в коде
struct Base { virtual ostream& F() { return std::cout << "Base"; } };
extern void magic_gop_swap( base* ); // тут может сидеть наш хак...struct Dir : Base { virtual ostream& F() { return std::cout << "Dir"; } };
void test_it()
{
Base& b = *new Dir;
b.F() << std::endl;
magic_gop_swap( &b )
b.F() << std::endl;
delete &b;
}
любой из этих компиляторов может, и имеет право, дооптимизироваться до кода эквивалентного такому:
CS>placement new C++ для замены vtbl это конечно не тот кунштюк который рекомендован для использования детям дошкольного возраста. CS>Просто надо знать что есть такая фича и она работает by C++ nature. Но использовать надо осознанно и по делу. Как всегда в C++ впрочем.
Есть мнение, что единственным делом, по которому это надо использовать осознанно является диверсия...
CS>На войне как на войне.
Зачем же жить "как на войне"? Может лучше НОРМАЛЬНУЮ архитектуру залудить в программе, чтобы хаки не требовались?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Во-первых, это всё вообще никак не связано с предыдущим.
Во-вторых, в С++ полимрфных векторов не бывает. Либо виртуализация, либо масив объектов. Сразу и то и то нельзя. Ну в смысле можно, конечно, но ССЗБ.
В частности, операция + для указателя и целого валидна только для указателей ВНУТРЬ ВЕКТОРА. Для указателей на одинокие объекты это UB... Вернее неспецифицированное, как мне кажется...
CS>А как же begin() и end() итераторы например?
А в чём там проблемы?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
E>Здравствуйте, c-smile, Вы писали:
Z>>>это хак в любом случае, в зависимости от того документированный он или нет можно только подставлять разные эпитеты... типа "грязный хак", например CS>>Я знаю use cases где это решение приносит значительный профит.
E>Поподробнее не расскажешь?
^^^^^^^^^^^^ SUBJ ^^^^^^^^^^^^
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, jazzer, Вы писали: J>Так что твоему хаку нужны какие-то гарантии со стороны компилятора, что именно так все и будет и этот mov вставляется перед каждым callq. У тебя такие гарантии есть?
Причём, что примечательно, такая оптимизация приводит к тому же эффекту, что достигается с помощью хака — уменьшение на 1 обращения к памяти. Но для всего кода, а не только хаченого. А теперь эту оптимизацию придётся отключить.
CS>Что мы получаем в этом случае: CS>1) нет поля handler* ph; меньше памяти нужно на instance CS>2) нет дополнительной косвенности вызова. CS>3) количество параметров вызова требуемое ph->do_layout_*** меньше на единицу...
CS>Поле handler* ph; в первом примере дублирует функционально VTBL в виде самописного лисапета. CS>А собственно зачем его дублировать когда оно есть и так?
...
Вам стоит дописать в кодстайл запрет на использование ссылок в "элементах". Пруф Вкратце есть огромная серая зона в стандарте, которая напрочь игнорирует существующие техники. Даже если бы вы удаляли элемент и создавали нового типа, но выделение дергали из пула то компилятор может не перепривязать ссылку. Что можно сказать ? Стандарты писать тяжело наверное и ошибки на том уровне ловить тем более.
Здравствуйте, Erop, Вы писали:
Z>>>>это хак в любом случае, в зависимости от того документированный он или нет можно только подставлять разные эпитеты... типа "грязный хак", например CS>>>Я знаю use cases где это решение приносит значительный профит.
E>>Поподробнее не расскажешь?
E>^^^^^^^^^^^^ SUBJ ^^^^^^^^^^^^
Да собственно ничего особо нового. Имплементация все того же DOM.
Скажем есть такая иерархия:
class node: object
{
element* parent; ...
virtual void each_visual( std::function<void(node*)> );
}
class element: node
{
collection<attribute> attributes;
collection<holder<node>> nodes;
virtual void each_visual( std::function<void(node*)> ); // need specializationvirtual void layout_width( ... ); // need specialization virtual void layout_height( ... ); // need specialization virtual void render( ... ); // need specialization
// еще примерно 30 методов которые могут быть специализированы
}
Есть парсер который строит дерево состоящее из element/node. Дерево абстрактное, содержит HTML, SVG и прочий XML в навал — то что называется HTML5 нынче. Парсер ничего не знает (физически не может знать) про то какие кокретно элементы создаются.
Через какое-то время подтягиваются стили в которых говорится что
input[type="text"] - текстовый редактор.
input[type="hidden"] - вообще не виден.
ul.mytable { display:table }
ul.mytable > li { display:table-row }
Т.е. *после* того как дерево построенно нам нужно элементы *специализировать*.
Скажем элемент который описан как display:table должен стать instance of этого:
Причем специализация сугубо динамическая. По ходу жизни элемент может стать
display:none и обратно например. Или в зависимости от размеров окна иметь flow:vertical или flow:horizontal.
Короче: нужно иметь возможность дешевой (быстрой и без memory overhead) специализации существующего DOM.
Желательно чтобы такая специализация не создавала лишние уровни косвенности в вызовах методов.
Мои эксперименты показывают profit, причем значительный если использовать технику смены vtbl для специализации.
Ну и код упрощается — становится более прозрачным. Немного позже смогу это выразить в цифрах если интересно.
Пока черновые прикидки в узких местах дают 12-16% выигрыш по скорости по сравнению с pimpl который используется в текущей версии.
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, k.o., Вы писали:
KO>>Ну, я, вроде бы как, указывал раздел стандарта, который это регламентирует. Зачем гадать, если можно посмотреть?
E>Нет стандарта в быстром доступе.
можно черновик C++0x посмотреть, там, это почти не изменилось: здесь, тоже 12.4/4.
KO>>>Минус за то, что этот код не отвечает на вопрос ТС о грязности хака и не решает проблему смены реализации без переаллокации. O>>А где вы тут видите "переаллокацию" при смене реализации? При смене там тока std::swap вызывается, который ниче не "переаллоцирует" KO>Ну, хорошо, ты решил проблему переаллокации, заранее выделив память для всех используемых реализаций. Ты действительно считаешь это хорошим решением? Кстати, интересно было бы увидеть как это будет выглядеть не для 2-х реализаций, а, хотя бы для 10.
Ну так а давайте писать код согласно требованиям, а не фантазиям. ТС хотел отсутствие реаллокации при смене реализации — он его получил. Если хочется минимизировать размер занятой памяти, — это уже совершенно иное требование, и решение будет тоже иное. К примеру, такое:
class Interface
{
public:
virtual void foo() = 0;
virtual ~Interface() = 0 {};
};
class InterfaceImpl1 : public Interface
{
public:
virtual void foo()
{
printf("InterfaceImpl1 this=0x%x\n", this);
}
};
class InterfaceImpl2 : public Interface
{
public:
virtual void foo()
{
printf("InterfaceImpl2 this=0x%x\n", this);
}
};
class Yanus
{
void *p;
Interface *i;
size_t l;
public:
Yanus(size_t prealloc_size = 0)
:p(0), i(0), l(0)
{
if (prealloc_size)
{
p = malloc(prealloc_size);
if (p) l = prealloc_size;
}
}
~Yanus()
{
if (i)i->~Interface();
}
Interface *operator->() { return i; }
const Interface *operator->() const { return i; }
template<class IFIMPL>
void ChangeFace() throw(std::bad_alloc)
{
if (i)
{
i->~Interface();
i = 0;
}
if (sizeof(IFIMPL)>l)
{
p = realloc(p, sizeof(IFIMPL));
if (!p)
{
l = 0;
throw std::bad_alloc();
}
l = sizeof(IFIMPL);
}
i = new (p) IFIMPL ();
}
};
void test_yanus()
{
Yanus y;
y.ChangeFace<InterfaceImpl1>();
y->foo();
y.ChangeFace<InterfaceImpl2>();
y->foo();
}
ну или хотябы такое:
template <class IMPL1, class IMPL2>
class Yanus
{
char buf[max(sizeof(IMPL1), sizeof(IMPL2))];
Interface *i;
public:
Yanus() : i(0)
{
}
~Yanus()
{
if (i)i->~Interface();
}
Interface *operator->() { return i; }
const Interface *operator->() const { return i; }
void ChangeFace(bool second)
{
if (i)
{
i->~Interface();
i = 0;
}
i = second ?
(Interface *)new (&buf[0]) IMPL1 ()
: (Interface *)new (&buf[0]) IMPL2 ();
}
};
Как много веселых ребят, и все делают велосипед...
Здравствуйте, Caracrist, Вы писали:
C>Здравствуйте, k.o., Вы писали:
KO>>Разумеется, тот код требует некоторого допиливания, хотя бы потому, что он не компилируется,
C>http://www.rsdn.ru/forum/cpp/4221864.aspx
Здравствуйте, c-smile, Вы писали:
Z>>это хак в любом случае, в зависимости от того документированный он или нет можно только подставлять разные эпитеты... типа "грязный хак", например CS>Я знаю use cases где это решение приносит значительный профит.
Поподробнее не расскажешь?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
R>>Что если во время конструирования нового объекта возникает исключение? Старый объект разрушен, новый не создан, буфер содержит мусор, объект-владелец оказывается в несогласованном состоянии. Как решить эту проблему при такой реализации я не представляю. C>Не вижу тут вообще ни какой проблемы C>
Такая мера, конечно, отчасти решает проблему. Но, тем не менее, работать с объектами проще, если обеспечена транзакционность операций над ними — операция либо завершается успешно, либо, в случае возникновения ошибки, состояние объекта не изменяется. Клиентскому коду тогда остается меньше возможных ситуаций, которые он должен уметь обрабатывать.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Аноним, Вы писали:
А>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>...
ужос.
Нафига вообще менять VPTR??
Здесь же просится паттерн стратегия:
// класс у которого хочется круто менять поведениеclass YourFlexibleObject {
private:
Strategy * strategy_;
public:
// вот этот метод меняет стратегиюvoid setStrategy(Strategy * strategy) { this->strategy_ = strategy; }
// интерфейсная часть, которую хочется менять:int getSmth(Arg1 arg1, Arg2 arg2) {
ASSERT_VALID(strategy_);
return strategy_->getSmth(this, arg1, arg2);
}
void doSmth(Arg3 arg3, Arg4 arg4) {
ASSERT_VALID(strategy_);
return strategy_->getSmth(this, arg3, arg4);
}
// ...
};
Strategy — интерфейс, который реализуют нужные тебе конкретные воплощения стратегии.
Эти экземпляры можно выделить один раз и переиспользовать без реаллокации — voila!
Здравствуйте, zaufi, Вы писали:
Z>Здравствуйте, c-smile, Вы писали:
CS>>Здравствуйте, Аноним, Вы писали:
А>>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>>способ примерно такой:
CS>>Это стандартная фича языка. Z>да?? а ссылочку на section в стандарте можно в студию?? %)
3.8 Object lifetime. Единственная, трудность может быть с обеспечением правильного выравнивания, но, если размеры объектов совпадают и память выделяется динамически, корректность гарантируется стандартом.
CS>>Поэтому хаком это является в том случае если этот момент у тебя не задокументирован должным образом.
Z>это хак в любом случае, в зависимости от того документированный он или нет можно только подставлять разные эпитеты... типа "грязный хак", например Z>ради чего делать себе жизнь сложнее и тем кому неповезет сопровождать этот код, когда можно легко обойтись "стандартным" (Behaviour) паттерном? Z>сложнее в том плане, что нужно очень аккуратно контролировать размер объектов, типы полей, заботиться о правильной переинициализации, аккуратно писать конструкторы внутренних классов, и все такое прочее... все это очень жестко завязано на конкрентую задачу и layout классов изменяющих поведение, и шаг вправо\лево -- расстрел памяти... ради чего? какой profit??
Здравствуйте, Caracrist, Вы писали:
C>Здравствуйте, rg45, Вы писали:
R>>А какие преимущества имеет твоя реализация? C>моя, это доработка идеи топикастера... за преимуществами в нему
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, k.o., Вы писали:
KO>>недвусмысленно намекает на то, что работать мы будем через интерфейс. Разумеется, тот код требует некоторого допиливания, хотя бы потому, что он не компилируется, но ТС сразу написал, что это только идея. Я, впрочем, не буду спорить, что говорить о замене vptr несколько некорректно, т.к. мы просто используем память существующего объекта для создания нового.
E>Что-то сильно я сомневаюсь, что можно так вольно обращаться с полями. В конце концов, например, деструктор класса позовёт деструктор поля, а там уже совсем и не то...
Ну, это уже претензии к c-smile, в исходном сообщении никаких полей не было, так что всё в порядке. Вообще это надо смотреть, что стандарт говорит по поводу POD полей (скорее всего ничего, т.е. это UB).
Здравствуйте, A13x, Вы писали:
A>После уже второго такого обсуждения о хаке виртуальной таблицы стало интересно — а не наткнутся ли любители переписывать таблицы виртуальных методов на грабли в виде девиртуализации проведенной оптимизирующем компилятором?
+1.
Одна из форм UB.
Поэтому и нельзя реюзать указатели/ссылки на погибший объект, даже если ты там сконструировал что-то новое и вроде как ссылки/указатели остались валидными — компилятор знает, что они указывали на изначальный класс, и вправе генерить какой угодно код, исходя из этого предположения.
Помимо девиртуализации вызова, он может, например, просто закешировать указатель на vtbl при первой инициализации указателя, а не считывать его каждый раз при обращении по указателю (зачем делать два джампа, когда достаточно одного?) и таким образом продолжить обращаться к старой таблице даже после подмены, с сопутствующими фейерверками.
Причем, из того, что второй фрагмент well-formed, отнюдь не следует, что первый также well-formed, поскольку никто не гарантирует равенства указателей obj1 и obj2.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Причем, из того, что второй фрагмент well-formed, отнюдь не следует, что первый также well-formed, поскольку никто не гарантирует равенства указателей obj1 и obj2.
К сожалению, даже и при равенстве не будет грантии работоспособности. Только при отключенном/отсутствующем оптимизаторе...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, rg45, Вы писали:
R>>Причем, из того, что второй фрагмент well-formed, отнюдь не следует, что первый также well-formed, поскольку никто не гарантирует равенства указателей obj1 и obj2.
E>К сожалению, даже и при равенстве не будет грантии работоспособности. Только при отключенном/отсутствующем оптимизаторе...
Это да. Я просто проблему, что называется, "беру по частям".
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Флейм также отчасти спровоцирован тем, что исходный пример содержит ошибку. Участники обсуждения тем или иным образом мысленно исправляют эту ошибку и делают это по-разному. Так возникла дискуссия между мной и k.o. — здесь
в ходе которой выявилась различная трактовка исходного примера.
Прикольно. А какие ещё трактовки есть?
Я так понял ТС, что он хотел бы сохранив старый указатель на базу, заиметь по этому адресу объект с модифицированным поведением. IMHO, это всё там довольно прозрачно восстанавливается.
А если придумать какой-то совсем другой вопрос где тоже есть new размещения, то можно много чего наобсуждать...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, ononim, Вы писали:
O>Хаком тут является отсутствие вызова деструктора и отсутствие проверки необходимого размера. Все. Для рассказывающих ужастики повторяю: ВСЕ.
В вот в том то и дело, что не всё ! Даже не рассматривая проблемы размера и деструктора в исходном примере есть хак.
Вот 4 варианта на псевдокоде:
Вариант N 0
class User {
public:
Interface intf;
template <class T> changeIntfRealization() {
new (&intf)T();
}
};
Вариант N 1
class User {
public:
char buf[достаточно большое число];
Interface* intf;
User() { intf = new (buf)T(); }
template <class T> changeIntfRealization() {
new (intf)T();
}
};
Вариант N 2
class User {
public:
char buf[достаточно большое число];
Interface* intf;
User() { intf = new (buf)T(); }
template <class T> changeIntfRealization() {
intf = new (intf)T();
}
};
Вариант N 3
class User {
public:
char buf[достаточно большое число];
Interface* intf;
User() { intf = new (buf)T(); }
template <class T> changeIntfRealization() {
intf = new (buf)T();
}
};
Разница в
0. new (&intf)T();
1. new (intf)T();
2. intf = new (intf)T();
3. intf = new (buf)T();
Каждый из вариантов при определённых обстоятельствах будет работать (без ошибок), но только последний вариант не является хаком.
0. new (&intf)T();
Нарушается пункт 3.8.7 стандарта (если T не совпадает с Interface)
1. new (intf)T();
Неявно предполагается, что (Interface*)pointer == (T*)pointer
2. intf = new (intf)T();
Ещё более неявно предполагается, что (Interface*)pointer == (T*)pointer. Если T имеет множественное наследование, то через некоторое количество таких перезаказов мы можем выйти за пределы буфера.
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, A13x, Вы писали:
A>>После уже второго такого обсуждения о хаке виртуальной таблицы стало интересно — а не наткнутся ли любители переписывать таблицы виртуальных методов на грабли в виде девиртуализации проведенной оптимизирующем компилятором? J>+1. J>Одна из форм UB. J>Поэтому и нельзя реюзать указатели/ссылки на погибший объект, даже если ты там сконструировал что-то новое и вроде как ссылки/указатели остались валидными — компилятор знает, что они указывали на изначальный класс, и вправе генерить какой угодно код, исходя из этого предположения. J>Помимо девиртуализации вызова, он может, например, просто закешировать указатель на vtbl при первой инициализации указателя, а не считывать его каждый раз при обращении по указателю (зачем делать два джампа, когда достаточно одного?) и таким образом продолжить обращаться к старой таблице даже после подмены, с сопутствующими фейерверками.
Здравствуйте, Seigram, Вы писали: S>1. Не ожидал такого обилия ответов и советов
А теперь их прочитайте. Причём особенно внимательно не те, что кричат «да, конечно можно!», а те, которые объясняют, почему так делать нельзя, чем это грозит и как сделать, чтобы было можно.
S>2. Многие просто не захотели заметить что я категорически НЕ могу шевелить кучу.
Решения без кучи предлагали. Заведите в мутирующем классе сырой буффер достаточного размера с запасом на выравнивание (char[...]), указатель на интерфейс и потом pimpl=new(area)T. Обязателен статический или динамический assert.
S> И плодить инстанции реализаций тоже не могу заранее.
Если у вас в производных классах добавляются новые члены-данные, то фейл очевиден. Если используются только данные из исходного мутирующего класса (или их вообще нет), то для чистых стратегий без данных достаточно одного инстанса на класс (и содержать он будет только VPTR). Код шаблона, хакающего VPTR, будет больше.
S>Но терзают сомнения — на всех ли компиляторах это прокатит ?..
Как уже говорили выше, могут быть проблемы при инициализации (указатели на разные классы могут не совпадать, выравнивание, размер), использовании (статическая девиртуализация или кеширование в рантайме), удалении (дефолный деструктор может делать неожиданные вещи). Поэтому не делайте так.
Здравствуйте, gegMOPO4, Вы писали: MOP>Здравствуйте, c-smile, Вы писали: CS>>Мои эксперименты показывают profit, причем значительный если использовать технику смены vtbl для специализации. CS>>Ну и код упрощается — становится более прозрачным. Немного позже смогу это выразить в цифрах если интересно. CS>>Пока черновые прикидки в узких местах дают 12-16% выигрыш по скорости по сравнению с pimpl который используется в текущей версии. MOP>Если использовать не pimpl, а impl (хранить не указатель на стратегию, а саму стратегию), то потерь из-за косвенности не будет. Кроме того, это позволит перегружать не только по display, но и по другим ортогональным стратегиям. И никаких хаков.
Э-э-э, что-то сглупил я. Не выйдет. Разве что реализовывать vtbl вручную.
Во всяком случае 12% (в узких местах) не стоят того, чтобы делать код настолько потенциально непереносимым и нерасширяемым. Есть более легальные способы борьбы за производительность.
Здравствуйте, gegMOPO4, Вы писали:
MOP>Во всяком случае 12% (в узких местах) не стоят того, чтобы делать код настолько потенциально непереносимым и нерасширяемым. Есть более легальные способы борьбы за производительность.
И переносим и расширяем. И 12% это много. И у всех разные критерии того что хорошо.
Скажем если ты работаешь в Гугле и предложишь способ поднять скорость поиска на 12% это примерно значит что Гугл сможет сэкономить на железе.
12% стоимости их железа это и есть профит.
Здравствуйте, c-smile, Вы писали: CS>Здравствуйте, gegMOPO4, Вы писали: MOP>>Во всяком случае 12% (в узких местах) не стоят того, чтобы делать код настолько потенциально непереносимым и нерасширяемым. Есть более легальные способы борьбы за производительность. CS>И переносим и расширяем. И 12% это много. И у всех разные критерии того что хорошо.
Эти 12% ведь были получены на синтетике? В составе большой программы, с учётом ввода/вывода/обработки, прирост будет много скромнее, на уровне погрешности (меньше 5% — можно во внимание не принимать, от сборки к сборке больше прыгать будет).
Но если вам так уж хочется использовать здесь хак, то, пожалуйста, вынесите его в отдельный класс display_type, без данных, и реализуйте всё внутри него, как в чёрном ящике. Чтобы легче было это дело локализировать и заменить, когда посыпется.
CS>Скажем если ты работаешь в Гугле и предложишь способ поднять скорость поиска на 12% это примерно значит что Гугл сможет сэкономить на железе. CS>12% стоимости их железа это и есть профит.
Когда через три года Гугл захочет мигрировать на армы, или компилироваться clang-ом, или в новых процессорах x86 реализуют какую-то фишку, а gcc её применит для оптимизации, и переход затянется на год, пока будут искать баги, возникшие по вашей вине, это будет не профит.
Здравствуйте, c-smile, Вы писали: CS>Что тебе сказать... 12% в моем случае это важно. И пять важно. CS>Тебе повезло что ты работаешь в бизнесе где 5% не важно и "от сборки к сборке больше прыгать" допускается.
Где-то в другом месте программы добавился или убрался оператор, сдвинулись функции, загрузчик иначе рандомизировал память — и уже время поплыло на несколько процентов. На современных компьютерах добиться стабильного результата невозможно.
CS>(Код выше выдран из .h файла генерерумого VS при создании COM классов — там всегда генерируется как C++ так и pure С формы интерфейсов)
Этот код написан производителем компилятора, который может гарантировать его работоспособность (на своём компиляторе). Вот когда вы будете поддерживать свой компилятор, то сможете безопасно лазить к нему в потроха (а лучше всего завести специальные прагмы и интрисики для переключения типа).
CS>Еще раз: зная как технически устроенна виртуальность можно её использовать для вящей пользы. CS>Аккуратно, с должной оберткой и в одном месте/декларации, а не по всему коду. CS>Можно конечно руками залезть и тот lpVtbl поменять на то что нужно. Но имхо new(instA) B() как раз более переносимо.
Хак остаётся хаком, о чём все и говорят. Вы хотя бы вынесите это в отдельное место и реализуйте нормальное безопасное решение, переключаемое условной компиляцией, чтобы легко можно было отключить. Ну и переключать так класс с данными — особенно рискованно. Сделайте, и правда, element без виртуальных функций, переключайте только handler.
Здравствуйте, gegMOPO4, Вы писали:
MOP>Да там всё понятно. Модификация — это наложение CSS, который меняет поведение. В частности, от свойства display зависит несколько методов. Причём методы эти для большинства имплементаций тривиальные (вроде return 0). Отсюда и получаются заметные без микроскопа 12% на обработке. Не уверен, правда, заметны ли они с учётом ввода/вывода и парсинга.
Дык вещи вроде display можно задавть просто флажком в элементе...
Мне не понятно, он HTML-viewer что ли пишет? Зачем? Ясно, что что-то похожее. Но прикольно, что больше никто такой хак не рвётся использовать.
MOP>Ну да, можно сэмулировать виртуальные таблицы вручную. Не так «красиво» и писанины больше, зато никаких претензий со стороны стандарта и никаких подводных камней. И предложенную jazzer гипотетическую оптимизацию (которая поломала бы хаченную версию) можно не ждать от компилятора, а безопасно провести вручную, что дало бы ещё 12%. Ну, это если кровь из носу надо.
Да не надо никаких таблиц и вообще всех этих С++ штук.
Ну просто на С пишем, но с контролем типов и всё
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Аноним, Вы писали:
А>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>способ примерно такой:
А>class User { // потребитель А>public: А> Interface intf; А> template <class T> changeIntfRealization() { А> // возможные проверки на происхождение T от Interface на манер: static_assert(is_base_of<Interface, T>::value, "Ouch"); А> new (&intf)T(); // происходит подмена VPTR А> } А>};
А>Собственно вопрос в топике.
UB. Вообще, хотя бы деструктор предыдущей реализации надо вызвать перед placement new, ну и позаботиться о том, чтобы хватило памяти (sizeof(intf) >= sizeof любой из реализаций) под все реализации. И это все равно не гарантирует отсутствия проблем при оптимизации.
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Тот кто сидит в пруду, Вы писали:
ТКС>Здравствуйте, Аноним, Вы писали:
ТКС>UB. Вообще, хотя бы деструктор предыдущей реализации надо вызвать перед placement new, ну и позаботиться о том, чтобы хватило памяти (sizeof(intf) >= sizeof любой из реализаций) под все реализации. И это все равно не гарантирует отсутствия проблем при оптимизации.
Деструктор, если он тривиальный, вызывать необязательно, UB будет только если размеры или выравние создаваемых объектов не соответсвуют используемой памяти.
Здравствуйте, k.o., Вы писали:
ТКС>>UB. Вообще, хотя бы деструктор предыдущей реализации надо вызвать перед placement new, ну и позаботиться о том, чтобы хватило памяти (sizeof(intf) >= sizeof любой из реализаций) под все реализации. И это все равно не гарантирует отсутствия проблем при оптимизации.
KO>Деструктор, если он тривиальный, вызывать необязательно
Так вроде никто тривиальных деструкторов не обещал
Одним из 33 полных кавалеров ордена "За заслуги перед Отечеством" является Геннадий Хазанов.
Здравствуйте, Тот кто сидит в пруду, Вы писали:
ТКС>Здравствуйте, k.o., Вы писали:
ТКС>>>UB. Вообще, хотя бы деструктор предыдущей реализации надо вызвать перед placement new, ну и позаботиться о том, чтобы хватило памяти (sizeof(intf) >= sizeof любой из реализаций) под все реализации. И это все равно не гарантирует отсутствия проблем при оптимизации.
KO>>Деструктор, если он тривиальный, вызывать необязательно
ТКС>Так вроде никто тривиальных деструкторов не обещал
В исходном сообщении они тривиальные. А если деструктор сам не вызывается, то можно и на не-тривиальные забивать, если, конечно, не нужно то, что они делают.
Здравствуйте, A13x, Вы писали:
А>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>... A>ужос. A>Нафига вообще менять VPTR?? A>Здесь же просится паттерн стратегия:
Что-то ваш паттерн стратегия напоминает паттерн Pimpl
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
KO>Минус за то, что этот код не отвечает на вопрос ТС о грязности хака и не решает проблему смены реализации без переаллокации.
А где вы тут видите "переаллокацию" при смене реализации? При смене там тока std::swap вызывается, который ниче не "переаллоцирует"
А на вопрос о "грязности" тут все ответили — добавлять особо нечего, а энтропию плодить, пересказывая уже сказанное — не хочу.
Как много веселых ребят, и все делают велосипед...
Здравствуйте, ononim, Вы писали:
KO>>Минус за то, что этот код не отвечает на вопрос ТС о грязности хака и не решает проблему смены реализации без переаллокации. O>А где вы тут видите "переаллокацию" при смене реализации? При смене там тока std::swap вызывается, который ниче не "переаллоцирует"
Ну, хорошо, ты решил проблему переаллокации, заранее выделив память для всех используемых реализаций. Ты действительно считаешь это хорошим решением? Кстати, интересно было бы увидеть как это будет выглядеть не для 2-х реализаций, а, хотя бы для 10.
Здравствуйте, ononim, Вы писали: KO>>>>Минус за то, что этот код не отвечает на вопрос ТС о грязности хака и не решает проблему смены реализации без переаллокации. O>>>А где вы тут видите "переаллокацию" при смене реализации? При смене там тока std::swap вызывается, который ниче не "переаллоцирует" KO>>Ну, хорошо, ты решил проблему переаллокации, заранее выделив память для всех используемых реализаций. Ты действительно считаешь это хорошим решением? Кстати, интересно было бы увидеть как это будет выглядеть не для 2-х реализаций, а, хотя бы для 10. O>Ну так а давайте писать код согласно требованиям, а не фантазиям. ТС хотел отсутствие реаллокации при смене реализации — он его получил. Если хочется минимизировать размер занятой памяти, — это уже совершенно иное требование, и решение будет тоже иное. К примеру, такое:
Ещё, код (точнее идея) продемонстрированный ТС позволял использовть произвольное количество реализаций интерфейса. Применяя твой подход, получим что для этого нужно всего лишь бесконечное количество памяти, а так, конечно, никаких переаллокаций, всё согласно требованиям.
Здравствуйте, Аноним, Вы писали:
А>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их,
А>Собственно вопрос в топике.
class Interface {
public:
virtual void someFun() =0;
virtual ~Interface(){}
};
class Derived1: public Interface {
public:
void someFun() {
//реализация...
}
};
class Derived2: public Interface {
public:
void someFun() {
//реализация...
}
};
class User { // потребительpublic:
char buff[sizeof(Interface)]; // at least sizeof(Interface)
Interface *intf;
User() : intf(0){
}
~User(){
Release();
}
template <class T>
void changeIntfRealization() {
static_assert(size_of(T) <= size_of(buff), "Ouch");
Release();
intf = new (&buff) T();
}
private:
void Release() {
if (intf) intf->~Interface();
intf = 0;
}
};
int main() {
User user;
if (!user.intf)
cout << "not initialized yet!" << endl;
user.changeIntfRealization<Derived1> ();
user.intf->someFun(); // вызовется реализация Derived1
user.changeIntfRealization<Derived2> ();
user.intf->someFun(); // вызовется реализация Derived2
}
Здравствуйте, c-smile, Вы писали:
CS>>>Это стандартная фича языка. Z>>да?? а ссылочку на section в стандарте можно в студию?? %)
CS>А "ссылочку" на что собственно?
На то, что реализация обязана использовать таблицу виртуальных методов. Откуда это следует?
А если нет , то тогда откуда следует, что можно безопасно размещать один объект по/в памяти другого объекта?
ЗЫ Приведённый в первом сообщении пример не скомпилируется.
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, c-smile, Вы писали:
CS>>>>Это стандартная фича языка. Z>>>да?? а ссылочку на section в стандарте можно в студию?? %)
CS>>А "ссылочку" на что собственно?
BFE>На то, что реализация обязана использовать таблицу виртуальных методов. Откуда это следует? BFE>А если нет , то тогда откуда следует, что можно безопасно размещать один объект по/в памяти другого объекта?
Здравствуйте, Аноним, Вы писали:
А>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>способ примерно такой:
А>
А> template <class T> changeIntfRealization() {
А> // возможные проверки на происхождение T от Interface на манер: static_assert(is_base_of<Interface, T>::value, "Ouch");
А> new (&intf)T(); // происходит подмена VPTR
А> }
А>
А>Собственно вопрос в топике.
Ну при поддержке будут проблемы.
Можно сделать чуть аккуратнее и звать перед new размещения ещё и виртуальный деструктор. Тогда будет почти и не хак, кроме того, можно ещё и проверять, что места хватит на заданный класс. В общем причесать, чтобы хотя бы на необязательные грабли не наступать.
Если почти забить на проблемы с выравниванием, то получится что-то типа
но это, IMHO, дело тухловатое. Ещё можно что-то похожее соорудить из буста. Только это ещё больше всё запутает.
Я не совсем понял. Есть ли в Derived1 и Derived2 ДАННЫЕ?
Если есть, то при переключении ты их теряешь, если нет, то на кой вообще всё это великолепие? Может просто обойтись полем, в котором указатель на метод/функцию лежит?
В общем, если ты расскажешь зачем такое надо, то может тебе посоветуют более традиционное решение...
Кроме того, тут как-то обсуждали в некотором роде смежный вопрос, может пригодится чего из того обсуждения
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Аноним, Вы писали:
А>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>способ примерно такой:
KO>>>>>Минус за то, что этот код не отвечает на вопрос ТС о грязности хака и не решает проблему смены реализации без переаллокации. O>>>>А где вы тут видите "переаллокацию" при смене реализации? При смене там тока std::swap вызывается, который ниче не "переаллоцирует" KO>>>Ну, хорошо, ты решил проблему переаллокации, заранее выделив память для всех используемых реализаций. Ты действительно считаешь это хорошим решением? Кстати, интересно было бы увидеть как это будет выглядеть не для 2-х реализаций, а, хотя бы для 10. O>>Ну так а давайте писать код согласно требованиям, а не фантазиям. ТС хотел отсутствие реаллокации при смене реализации — он его получил. Если хочется минимизировать размер занятой памяти, — это уже совершенно иное требование, и решение будет тоже иное. К примеру, такое: KO>Ещё, код (точнее идея) продемонстрированный ТС позволял использовть произвольное количество реализаций интерфейса. Применяя твой подход, получим что для этого нужно всего лишь бесконечное количество памяти, а так, конечно, никаких переаллокаций, всё согласно требованиям.
Для бесконечного количества реализаций интерфейса по любому надо бесконечное количество памяти, так что не надо считать объем сфероконя без яиц.
Как много веселых ребят, и все делают велосипед...
Здравствуйте, ononim, Вы писали:
O>Для бесконечного количества реализаций интерфейса по любому надо бесконечное количество памяти, так что не надо считать объем сфероконя без яиц.
ROM, но не RAM! А это не всегда одно и то же...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, zaufi, Вы писали:
Z>вопервых, уважаемый k.o. ссылавшийся на 3.8 стандарта: Z>Вы его читали внимательно???
Можно на ты, и давай без наездов, errare humanum est, если я где-то ошибся аргументируй.
Z>3.8.1: Z>
Z>The lifetime of an object of type T ends when:
Z>— if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
Z>— the storage which the object occupies is reused or released.
Z>выделенное как раз наш случай! как только есть попытка переиспользовать память из-под объекта -- все! считается что его lifetime закончен!
Ну и отлично, после это мы используем память для размещения нового объекта и используем новый объект, какие проблемы?
Z>и далее медитируем начиная с 3.8.1 и до упора -- на каждом шагу упоминается про попытки реюзнуть сторыдж из-под класса и как это приводит к UB!
К UB приводит не переиспользование памяти, а использование объекта после того как его время жизни закончилось.
Z>в частности, давате всмотримся в пример кода из 3.8.5:
Здравствуйте, k.o., Вы писали:
KO>Здравствуйте, B0FEE664, Вы писали:
BFE>>Здравствуйте, c-smile, Вы писали:
CS>>>>>Это стандартная фича языка. Z>>>>да?? а ссылочку на section в стандарте можно в студию?? %)
CS>>>А "ссылочку" на что собственно?
BFE>>На то, что реализация обязана использовать таблицу виртуальных методов. Откуда это следует? BFE>>А если нет , то тогда откуда следует, что можно безопасно размещать один объект по/в памяти другого объекта?
KO>здесь
3.8.7 If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
— the storage for the new object exactly overlays the storage location which the original object occupied, and
— the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
— the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
— the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects)
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, k.o., Вы писали:
KO>>Здравствуйте, B0FEE664, Вы писали:
BFE>>>Здравствуйте, c-smile, Вы писали:
CS>>>>>>Это стандартная фича языка. Z>>>>>да?? а ссылочку на section в стандарте можно в студию?? %)
CS>>>>А "ссылочку" на что собственно?
BFE>>>На то, что реализация обязана использовать таблицу виртуальных методов. Откуда это следует? BFE>>>А если нет , то тогда откуда следует, что можно безопасно размещать один объект по/в памяти другого объекта?
KO>>здесь
BFE>Хмм:
BFE>3.8.7 If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:
Трюк в том, чтобы не использовать ссылки и указатели на старый объект. Поскольку мы работаем с объектами через указатели на интерфейс дальнейшее можно не учитывать. В самом деле, чем, по твоему это отличается от
Interface * i = 0;
void * storage = malloc(<size of storage for any implementation>);
Impl1 * impl1 = new (storage) Impl1;
i = impl1;
i->foo();
i->~Interface();
Impl2 * impl2 = new (storage) Impl2;
i = impl2;
i->foo();
i->~Interface();
free(storage);
Здравствуйте, k.o., Вы писали:
Z>>выделенное как раз наш случай! как только есть попытка переиспользовать память из-под объекта -- все! считается что его lifetime закончен!
KO>Ну и отлично, после это мы используем память для размещения нового объекта и используем новый объект, какие проблемы?
А давай разберем простенький пример.
class Interface {/*...*/};
class Implementation1 : public Interface {/*...*/};
class Implementation2 : public Interface {/*...*/};
Interface* i1 = new Implementation1();
Interface* i2 = new(i1) Implementation2();
i2->foo(); //Ok
i1->foo(); //???
Ты говоришь, используем новый, и нет проблем, замечательно. И если можно использовать i2, то можно использовать и i1... но при условии (!), что i1 == i2. А откуда, собственно, уверенность в последнем?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали: R>Здравствуйте, k.o., Вы писали: Z>>>выделенное как раз наш случай! как только есть попытка переиспользовать память из-под объекта -- все! считается что его lifetime закончен! KO>>Ну и отлично, после это мы используем память для размещения нового объекта и используем новый объект, какие проблемы? R>А давай разберем простенький пример.
Скрытый текст
R>class Interface {/*...*/};
R>class Implementation1 : public Interface {/*...*/};
R>class Implementation2 : public Interface {/*...*/};
R>Interface* i1 = new Implementation1();
R>Interface* i2 = new(i1) Implementation2();
i2->>foo(); //Ok
i1->>foo(); //???
R>Ты говоришь, используем новый, и нет проблем, замечательно. И если можно использовать i2, то можно использовать и i1... но при условии (!), что i1 == i2. А откуда, собственно, уверенность в последнем?
Скорее всего ниоткуда, только что из этого следует? Не надо использовать старое значение i1.
Здравствуйте, k.o., Вы писали:
KO>>>Деструктор, если он тривиальный, вызывать необязательно
Это где такое написано? Тривиальный деструктор, сгенерированный компилятором, обеспечивает вызов деструкторов агрегированных объектов — контейнеров, строк, умных указателей и т.д. и т.п., и не вызывать его — это не правильно.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, k.o., Вы писали:
R>>Ты говоришь, используем новый, и нет проблем, замечательно. И если можно использовать i2, то можно использовать и i1... но при условии (!), что i1 == i2. А откуда, собственно, уверенность в последнем?
KO>Скорее всего ниоткуда, только что из этого следует? Не надо использовать старое значение i1.
Правильно. Таким образом, после "подмены vptr" объект использовать не надо, ч.т.д.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
KO>>>>Деструктор, если он тривиальный, вызывать необязательно
R>Это где такое написано? Тривиальный деструктор, сгенерированный компилятором, обеспечивает вызов деструкторов агрегированных объектов — контейнеров, строк, умных указателей и т.д. и т.п., и не вызывать его — это не правильно.
12.4 пункты 3 и 4
A destructor is trivial if it is an implicitly-declared destructor and if:
— all of the direct base classes of its class have trivial destructors and
— for all of the non-static data members of its class that are of class type (or array thereof), each such class
has a trivial destructor.
Otherwise, the destructor is non-trivial.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
R>>>Ты говоришь, используем новый, и нет проблем, замечательно. И если можно использовать i2, то можно использовать и i1... но при условии (!), что i1 == i2. А откуда, собственно, уверенность в последнем?
KO>>Скорее всего ниоткуда, только что из этого следует? Не надо использовать старое значение i1.
R>Правильно. Таким образом, после "подмены vptr" объект использовать не надо, ч.т.д.
Смотря что понимать под выделенным. Если что-то вроде твоего примера, то не надо, если что-то другое, то вполне можно:
class Interface {/*...*/};
class Implementation1 : public Interface {/*...*/};
class Implementation2 : public Interface {/*...*/};
Interface * i1 = 0;
aligned_storage<sizeof(<max implementation size>)>::type storage;
// ...
i1 = new (&storage) Implementation1();
i1->foo();
// ...
i1 = new (&storage) Implementation2();
i1->foo();
Здравствуйте, k.o., Вы писали:
KO>12.4 пункты 3 и 4
KO>
KO>A destructor is trivial if it is an implicitly-declared destructor and if:
KO>— all of the direct base classes of its class have trivial destructors and
KO>— for all of the non-static data members of its class that are of class type (or array thereof), each such class
KO>has a trivial destructor.
KO>Otherwise, the destructor is non-trivial.
Ну допустим. Таким образом, пришли к тому, что для использования такого приема требуется чтобы:
Ни в одном из классов в иерархии наследования не было определенных пользователем деструкторов
Ни в одном из классов в иерархии наследования не было полей с нетривиальным деструктором
Ну и зачем оно такое счастье?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, k.o., Вы писали:
R>>Правильно. Таким образом, после "подмены vptr" объект использовать не надо, ч.т.д.
KO>Смотря что понимать под выделенным. Если что-то вроде твоего примера, то не надо, если что-то другое, то вполне можно: KO>
Ну это уже несколько другой use case, чем в исходном посте. Изначальная идея — в существующем полиморфном объекте подменить реализацию вместе с таблицей виртуальных функций и продолжать использовать тот же объект.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
R>>>Правильно. Таким образом, после "подмены vptr" объект использовать не надо, ч.т.д.
KO>>Смотря что понимать под выделенным. Если что-то вроде твоего примера, то не надо, если что-то другое, то вполне можно:
R>Ну это уже несколько другой use case, чем в исходном посте. Изначальная идея — в существующем полиморфном объекте подменить реализацию вместе с таблицей виртуальных функций и продолжать использовать тот же объект.
Я такого в исходном сообщении не увидел. Мне показалось, что
Interface intf;
недвусмысленно намекает на то, что работать мы будем через интерфейс. Разумеется, тот код требует некоторого допиливания, хотя бы потому, что он не компилируется, но ТС сразу написал, что это только идея. Я, впрочем, не буду спорить, что говорить о замене vptr несколько некорректно, т.к. мы просто используем память существующего объекта для создания нового.
Здравствуйте, k.o., Вы писали:
KO>Здравствуйте, rg45, Вы писали:
R>>Здравствуйте, k.o., Вы писали:
R>>>>Правильно. Таким образом, после "подмены vptr" объект использовать не надо, ч.т.д.
KO>>>Смотря что понимать под выделенным. Если что-то вроде твоего примера, то не надо, если что-то другое, то вполне можно:
R>>Ну это уже несколько другой use case, чем в исходном посте. Изначальная идея — в существующем полиморфном объекте подменить реализацию вместе с таблицей виртуальных функций и продолжать использовать тот же объект.
KO>Я такого в исходном сообщении не увидел. Мне показалось, что KO>
KO>Interface intf;
KO>недвусмысленно намекает на то, что работать мы будем через интерфейс. Разумеется, тот код требует некоторого допиливания, хотя бы потому, что он не компилируется, но ТС сразу написал, что это только идея. Я, впрочем, не буду спорить, что говорить о замене vptr несколько некорректно, т.к. мы просто используем память существующего объекта для создания нового.
Ну, видимо, "допилили" мы этот код по-разному. Я это сделал так:
Interface* intf;
//...
intf = new (intf) T(); // происходит подмена VPTR
Но ты уже вроде бы признал, что так делать некорректно.
А как еще можно "допилить" это пример? Если "мы просто используем память существующего объекта для создания нового", то какие типы могут иметь существующий и новый объекты?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, A13x, Вы писали:
А>>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>>... A>>ужос. A>>Нафига вообще менять VPTR?? A>>Здесь же просится паттерн стратегия: V>Что-то ваш паттерн стратегия напоминает паттерн Pimpl
pimpl да не тот
В моем понимание паттерна "стратегия" в данном конкретном случае использование этого паттерна не обязательно предполагает перенос всей реализации класса в стратегию, т.е. мы не обязательно должны менять поведение класса целиком и полностью.
Т.е. у класса может быть ряд методов не нуждающихся в смене реализации + к тому ряд методов предполагающих использование стратегии могут предлагать реализацию по умолчанию, если стратегия не задана.
Вот утрированный пример (не из жизни):
class ResultSet {
private:
Strategy s;
DataProvider p;
public:
Row getRow(int index) {
if (s != null) {
// иное поведение если есть стратегия получения (например из кэша)return s->getRow(p, index);
}
return rowCollection->fetchRow(index);
}
void applySecurityConstraint(SecurityConstraint c) {
// а вот этот метод, положим, немыслим без стратегии
ASSERT_VALID(s);
s->applySecurityConstraint(p, c);
}
RowMetadata getRowMetadata() {
// а для этого метода нет какой-то особой стратегииreturn p->getRowMetadata();
}
}
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
KO>>Здравствуйте, rg45, Вы писали:
R>>>Здравствуйте, k.o., Вы писали:
R>>>>>Правильно. Таким образом, после "подмены vptr" объект использовать не надо, ч.т.д.
KO>>>>Смотря что понимать под выделенным. Если что-то вроде твоего примера, то не надо, если что-то другое, то вполне можно:
R>>>Ну это уже несколько другой use case, чем в исходном посте. Изначальная идея — в существующем полиморфном объекте подменить реализацию вместе с таблицей виртуальных функций и продолжать использовать тот же объект.
KO>>Я такого в исходном сообщении не увидел. Мне показалось, что KO>>
KO>>Interface intf;
KO>>недвусмысленно намекает на то, что работать мы будем через интерфейс. Разумеется, тот код требует некоторого допиливания, хотя бы потому, что он не компилируется, но ТС сразу написал, что это только идея. Я, впрочем, не буду спорить, что говорить о замене vptr несколько некорректно, т.к. мы просто используем память существующего объекта для создания нового.
R>Ну, видимо, "допилили" мы этот код по-разному. Я это сделал так: R>
R>Interface* intf;
R>//...
R>intf = new (intf) T(); // происходит подмена VPTR
R>
R>Но ты уже вроде бы признал, что так делать некорректно.
Ну, я думал, что сделать ещё один небольшой допил будет несложно:
Хотя, при некоторых условиях будет работать даже тот код, который ты только что написал. Не забывай, что в том примере, который ты предлагал обсудить, не было обновления intf после создания нового объекта.
R>А как еще можно "допилить" это пример? Если "мы просто используем память существующего объекта для создания нового", то какие типы могут иметь существующий и новый объекты?
Любые типы для которых подходит размер и выравнивание используемого участка памяти, и для которых можно не вызывать деструктор (или добавляем вызов оного перед new).
Здравствуйте, k.o., Вы писали:
R>>Ну допустим. Таким образом, пришли к тому, что для использования такого приема требуется чтобы: KO>Не "допустим", а таково требование стандарта, можно подумать, кто-то умрёт если ты прямо напишешь что ошибся.
Признаю, ошибся.
ТКС>>UB. Вообще, хотя бы деструктор предыдущей реализации надо вызвать перед placement new, ну и позаботиться о том, чтобы хватило памяти (sizeof(intf) >= sizeof любой из реализаций) под все реализации. И это все равно не гарантирует отсутствия проблем при оптимизации.
KO>Деструктор, если он тривиальный, вызывать необязательно, UB будет только если размеры или выравние создаваемых объектов не соответсвуют используемой памяти.
KO>Если да, то я нигде не призывал, отказаться от вызова деструкторов, а всего лишь выразил сомнение в том, что обязательно будет UB.
А вот здесь я бы акцент все-таки поставил бы по-другому. UB не будет только в том случае, если есть гарантия того, что ни в одном из классов иерархии нет нетривиальных деструкторов. Вряд ли такое требование можно назвать типичным для иерархий с абстрактными классами во главе. В исходном сообщении такого ограничения также не накладывается — строчки "//реализация..." говорят о том, что в производных классах могут присутстовать члены с нетривиальными деструкторами. Видимо, Тот-кто-сидит-в-пруду, когда говорил о необходимости вызова деструкторов, также в качестве общего случая рассматривал (вполне обоснованно) ситуацию, когда классы имеют нетривиальные деструкторы.
--
Справедливость выше закона. А человечность выше справедливости.
KO>Хотя, при некоторых условиях будет работать даже тот код, который ты только что написал. Не забывай, что в том примере, который ты предлагал обсудить, не было обновления intf после создания нового объекта.
Ну можно еще пофантазировать, как тебе такой вариант:
Interface& intf;
//...new (&intf) T(); // происходит подмена VPTR
Этот вариант разбомбить тоже не сложно.
Но вот чем бы не хотелось заниматься, так это соревноваться в том, у кого правильней работает фантазия. В конце-концов пусть ТС предоставит компилируемый (хотя бы) пример, тогда уже и будем обсуждать.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, k.o., Вы писали:
R>Но вот чем бы не хотелось заниматься, так это соревноваться в том, у кого правильней работает фантазия. В конце-концов пусть ТС предоставит компилируемый (хотя бы) пример, тогда уже и будем обсуждать.
Здравствуйте, Caracrist, Вы писали:
А>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>Собственно вопрос в топике.
Ну вот, другое дело. Только вряд ли теперь "замена VPTR" является удачным названием для такой реализации. Это просто многократное использование буфера в качестве хранилища для объектов.
Что не очень хорошо — это небезопасность с точки зрения исключений:
Что если во время конструирования нового объекта возникает исключение? Старый объект разрушен, новый не создан, буфер содержит мусор, объект-владелец оказывается в несогласованном состоянии. Как решить эту проблему при такой реализации я не представляю.
И еще, в качестве аргумента помещающего new я бы использовал не адрес массива, а просто массив:
intf = new (buff) T();
Здесь массив неявно преобразуется к указателю на первый элемент... и короче и спокойнее как-то, хотя не принципиально, наверное.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, k.o., Вы писали:
KO>Трюк в том, чтобы не использовать ссылки и указатели на старый объект. Поскольку мы работаем с объектами через указатели на интерфейс дальнейшее можно не учитывать. В самом деле, чем, по твоему это отличается от KO>
KO>Interface * i = 0;
KO>void * storage = malloc(<size of storage for any implementation>);
KO>Impl1 * impl1 = new (storage) Impl1;
KO>i = impl1;
i->>foo();
i->>~Interface();
KO>Impl2 * impl2 = new (storage) Impl2;
KO>i = impl2;
i->>foo();
i->>~Interface();
KO>free(storage);
KO>
Это отличается заказом памяти. Собственно разница состоит в "malloc(<size of storage for any implementation>);"
А занимаясь такими вещами, мы делаем шаг в сторону С-- (здесь) или же в сторону Майкросовтовского CoCreateInstance.
Чем, собственно, не устраивает классический подход :
class User { // потребительpublic:
Interface* intf;
template <class T> changeIntfRealization() {
delete intf;
intf = new T;
}
};
Здравствуйте, Caracrist, Вы писали:
А>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>Собственно вопрос в топике.
А вот теперь интересный вопрос: а ради чего сыр-бор? Вот простая, понятная, компактная, безопасная с точки зрения исключений реализация:
using boost::shared_ptr;
class User { // потребительpublic:
shared_ptr<Interface> intf;
User() {/*Не нужен*/}
~User(){/*Не нужен*/}
template <class T>
void changeIntfRealization() {
intf.reset(new T());
}
};
А какие преимущества имеет твоя реализация?
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, k.o., Вы писали:
BFE>Это отличается заказом памяти. Собственно разница состоит в "malloc(<size of storage for any implementation>);"
И что такого волшебного в malloc? Можно использовать и new char[<size of storage for any implementation>] или вообще aligned_storage<(size of storage for any implementation)>::type storage;
BFE>А занимаясь такими вещами, мы делаем шаг в сторону С-- (здесь) или же в сторону Майкросовтовского CoCreateInstance.
Какими такими?
BFE>Чем, собственно, не устраивает классический подход :
Позволю себе процитировать ТС:
Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, Caracrist, Вы писали:
А>>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>>Собственно вопрос в топике.
R>Ну вот, другое дело. Только вряд ли теперь "замена VPTR" является удачным названием для такой реализации. Это просто многократное использование буфера в качестве хранилища для объектов.
R>Что не очень хорошо — это небезопасность с точки зрения исключений: R>
R>Что если во время конструирования нового объекта возникает исключение? Старый объект разрушен, новый не создан, буфер содержит мусор, объект-владелец оказывается в несогласованном состоянии. Как решить эту проблему при такой реализации я не представляю.
Не вижу тут вообще ни какой проблемы
void Release() {
if (intf) intf->~Interface();
intf = 0;
}
R>И еще, в качестве аргумента помещающего new я бы использовал не адрес массива, а просто массив: R>
R>intf = new (buff) T();
R>
R>Здесь массив неявно преобразуется к указателю на первый элемент... и короче и спокойнее как-то, хотя не принципиально, наверное.
на самом деле тут очепятка
Здравствуйте, rg45, Вы писали:
R>Здравствуйте, Caracrist, Вы писали:
А>>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их, А>>>Собственно вопрос в топике.
R>А вот теперь интересный вопрос: а ради чего сыр-бор? Вот простая, понятная, компактная, безопасная с точки зрения исключений реализация:
Вот не менее простая и безопасная реализация:
Interface*
// а также
//shared_ptr<Interface>
//auto_ptr<Interface>
Здравствуйте, Caracrist, Вы писали:
R>>А вот теперь интересный вопрос: а ради чего сыр-бор? Вот простая, понятная, компактная, безопасная с точки зрения исключений реализация: C>Вот не менее простая и безопасная реализация: C>
C>Interface*
C>// а также
C>//shared_ptr<Interface>
C>//auto_ptr<Interface>
C>
C>
Ну c shared_ptr действительно все хорошо и просто, именно этот вариант я и описал. А вот с оставшимися двумя вариантами — голым указателем и auto_ptr — не все так просто и однозначно, как может показаться на первый взгляд. Достаточно вспомнить о генерируемых по умолчанию конструкторе копии и копирующем операторе присваивания. Поведение этих функций членов, сгенерированных компилятором, не всегда будет желаемым, а порой может просто приводить к ошибкам.
--
Справедливость выше закона. А человечность выше справедливости.
Здравствуйте, A13x, Вы писали:
A>pimpl да не тот
Один в один. Дамаю, основная масса паттернов переделка базовых паттернов, т.е. по сути тоже что и прежде, только в профиль.
A>В моем понимание паттерна "стратегия" в данном конкретном случае использование этого паттерна не обязательно предполагает перенос всей реализации класса в стратегию, т.е. мы не обязательно должны менять поведение класса целиком и полностью.
A>Т.е. у класса может быть ряд методов не нуждающихся в смене реализации + к тому ряд методов предполагающих использование стратегии могут предлагать реализацию по умолчанию, если стратегия не задана. A>Вот утрированный пример (не из жизни):
Именно что утрированный, да и усложнённый. Того же можно достичь и через это:
class Strategy : public StrategyBase;
И размер ResultSet не увеличился.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, rg45, Вы писали:
R> R>Ни в одном из классов в иерархии наследования не было определенных пользователем деструкторов R>Ни в одном из классов в иерархии наследования не было полей с нетривиальным деструктором R>R>Ну и зачем оно такое счастье?
Это всё пустое, так как для тривиальности деструктора требуется, так же, отсутствие виртуальности...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, k.o., Вы писали:
KO>Если да, то я нигде не призывал, отказаться от вызова деструкторов, а всего лишь выразил сомнение в том, что обязательно будет UB.
1) формулировка странная. Так как UB -- это формальное состояние, когда с авторов реализации стандарт снимает всякую ответственность за последствия, то оно не может быть или не быть. Просто оно не всегда приводит к нежелательным последствиям...
2) Всё это ваше хаккерство опирается на то предположение, что если мы по одному и тому же адресу создадим такой выведенный объект или сякой, и приведём и тот и другой к указателю на базу, то получим один и тот же адрес. Гарантий этого на самом деле нет...
Ну и вообще в стандарте вроде есть прямой запрет на продолжение использования указателя на объект, после окончания времени жизни объекта...
Например, можно представить себе реализацию С++ (скажем интерпретатор), которая для пущей надёжности в каждом указателе хранит не только смещение относительно 0 плоской модели памяти, но и номер аллокации либо ещё какое-то ID, валидность которого проверяет при доступе...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, k.o., Вы писали:
KO>недвусмысленно намекает на то, что работать мы будем через интерфейс. Разумеется, тот код требует некоторого допиливания, хотя бы потому, что он не компилируется, но ТС сразу написал, что это только идея. Я, впрочем, не буду спорить, что говорить о замене vptr несколько некорректно, т.к. мы просто используем память существующего объекта для создания нового.
Что-то сильно я сомневаюсь, что можно так вольно обращаться с полями. В конце концов, например, деструктор класса позовёт деструктор поля, а там уже совсем и не то...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, A13x, Вы писали:
A>>pimpl да не тот V>Один в один. Дамаю, основная масса паттернов переделка базовых паттернов, т.е. по сути тоже что и прежде, только в профиль.
Насчет переделки согласен, вопрос по большему счету философский. Кстати, в википедии вот пример тоже до подозрительности напоминает банальный pointer to implementation — http://en.wikipedia.org/wiki/Strategy_pattern
Здравствуйте, rg45, Вы писали:
R>А вот здесь я бы акцент все-таки поставил бы по-другому. UB не будет только в том случае, если есть гарантия того, что ни в одном из классов иерархии нет нетривиальных деструкторов. Вряд ли такое требование можно назвать типичным для иерархий с абстрактными классами во главе. В исходном сообщении такого ограничения также не накладывается — строчки "//реализация..." говорят о том, что в производных классах могут присутстовать члены с нетривиальными деструкторами. Видимо, Тот-кто-сидит-в-пруду, когда говорил о необходимости вызова деструкторов, также в качестве общего случая рассматривал (вполне обоснованно) ситуацию, когда классы имеют нетривиальные деструкторы.
Может быть меня конечно глючит, например я могу путать стандарт и MSDN, но я точно помню, что в одном из этих двух документов утверждалось, что для тривиальности деструктора нужно ещё и отсутствие виртуальных баз и методов. Так что рассматриваемый случай с наследниками абстрактных классов совершенно точно не относится к тривиальным деструкторам...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, rg45, Вы писали:
E>Может быть меня конечно глючит, например я могу путать стандарт и MSDN, но я точно помню, что в одном из этих двух документов утверждалось, что для тривиальности деструктора нужно ещё и отсутствие виртуальных баз и методов. Так что рассматриваемый случай с наследниками абстрактных классов совершенно точно не относится к тривиальным деструкторам...
Ну, я, вроде бы как, указывал раздел стандарта, который это регламентирует. Зачем гадать, если можно посмотреть?
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, k.o., Вы писали:
KO>>Если да, то я нигде не призывал, отказаться от вызова деструкторов, а всего лишь выразил сомнение в том, что обязательно будет UB. E>1) формулировка странная. Так как UB -- это формальное состояние, когда с авторов реализации стандарт снимает всякую ответственность за последствия, то оно не может быть или не быть. Просто оно не всегда приводит к нежелательным последствиям...
Ну, учитывая, что исходный код даже не компилируется, говорить о наличии в нём UB не совсем корректно. Если же мы рассматриваем развитие этой идеи, то реальный код может быть разным, может быть с UB, может быть и без него. Это я и имел в виду говоря что там не обязательно будет UB.
E>2) Всё это ваше хаккерство опирается на то предположение, что если мы по одному и тому же адресу создадим такой выведенный объект или сякой, и приведём и тот и другой к указателю на базу, то получим один и тот же адрес. Гарантий этого на самом деле нет...
это не совсем так, мы с rg45 это уже обсуждали, чтобы не повторяться смотри здесь
Здравствуйте, k.o., Вы писали:
KO>Ну, я, вроде бы как, указывал раздел стандарта, который это регламентирует. Зачем гадать, если можно посмотреть?
Нет стандарта в быстром доступе.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, k.o., Вы писали:
KO>есть, но использовать указатель на новый объект, пусть и размещённый на месте старого, можно.
Да, но гарантий того, что эти указатели совпадут таки нет...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, k.o., Вы писали:
KO>>есть, но использовать указатель на новый объект, пусть и размещённый на месте старого, можно.
E>Да, но гарантий того, что эти указатели совпадут таки нет...
Так нам их и не надо, смотри по ссылке, что я тебе давал. Там, конечно, много букв, но, от этого ещё меньше хочется всё заново повторять.
Здравствуйте, k.o., Вы писали:
KO>Здравствуйте, Erop, Вы писали:
E>>Здравствуйте, k.o., Вы писали:
KO>>>есть, но использовать указатель на новый объект, пусть и размещённый на месте старого, можно.
это всё просто неудачное название темы, на самом деле, мы просто используем одно хранилище для создания в нём разных объектов, никакой чёрной магии.
E>>Да, но гарантий того, что эти указатели совпадут таки нет...
KO>Так нам их и не надо, смотри по ссылке, что я тебе давал. Там, конечно, много букв, но, от этого ещё меньше хочется всё заново повторять.
Здравствуйте, k.o., Вы писали:
KO>Здравствуйте, Erop, Вы писали:
E>>Здравствуйте, k.o., Вы писали:
KO>>>недвусмысленно намекает на то, что работать мы будем через интерфейс. Разумеется, тот код требует некоторого допиливания, хотя бы потому, что он не компилируется, но ТС сразу написал, что это только идея. Я, впрочем, не буду спорить, что говорить о замене vptr несколько некорректно, т.к. мы просто используем память существующего объекта для создания нового.
E>>Что-то сильно я сомневаюсь, что можно так вольно обращаться с полями. В конце концов, например, деструктор класса позовёт деструктор поля, а там уже совсем и не то...
KO>Ну, это уже претензии к c-smile, в исходном сообщении никаких полей не было, так что всё в порядке. Вообще это надо смотреть, что стандарт говорит по поводу POD полей (скорее всего ничего, т.е. это UB).
в C++0x можно (не будет UB) обращаться к полям класса с тривиальным деструктором после того как объект был разрушен. Правда, о том, что значения этих полей останутся прежними ничего не говорится.
Здравствуйте, k.o., Вы писали:
KO>И что такого волшебного в malloc? Можно использовать и new char[<size of storage for any implementation>] или вообще aligned_storage<(size of storage for any implementation)>::type storage;
Можно. В C++ вполне можно написать свой аллокатор и , вероятно, за счёт дополнительного знания об объектах он будет работать быстрее стандартного.
BFE>>А занимаясь такими вещами, мы делаем шаг в сторону С-- (здесь) или же в сторону Майкросовтовского CoCreateInstance.
KO>Какими такими?
Ну вот когда начинают думать об объектах не в абстрактных терминах , а в терминах объёма памяти и кусках адресных пространств, вот тогда и начинается... А на выходе получается (обычно) непереносимый код. Или же что-то вроде management С++, в котором простая операция доступа через указатель (вроде a->b) в шесь раз медленнее...
BFE>>Чем, собственно, не устраивает классический подход :
KO>Позволю себе процитировать ТС: KO>
KO>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их
Вот собственно это и интересно. Чем вызвано такое требование ? Это ограничение связано с реал-тайм или с контролем памяти ? Или же тут что-то ещё ? Или стандартный аллокатор настолько плох ?
Здравствуйте, Erop, Вы писали:
E>Что-то сильно я сомневаюсь, что можно так вольно обращаться с полями. В конце концов, например, деструктор класса позовёт деструктор поля, а там уже совсем и не то...
Здравствуйте, A13x, Вы писали:
A>После уже второго такого обсуждения о хаке виртуальной таблицы стало интересно — а не наткнутся ли любители переписывать таблицы виртуальных методов на грабли в виде девиртуализации проведенной оптимизирующем компилятором?
A>Знаю, что современные компиляторы в ряде случаев могут использовать т.н. девиртуализацию — прямой вызов функции (или даже inline подстановка) в месте вызова функции.
Хотел бы я посмотреть на компилятор делающий "девиртуализацию" COM или Corba интерфейсов которые есть сугубо abstract classes.
COM и Corba интерфейсы имеют форму понимаемую и C++ и C. Т.е. всегда возможно получить доступ к vtbl полю. Хотя бы зайдя через C.
Конечно это будет хак и ересь с точки зрения Vovan the Programmer. Но с точки зрения разработчика COM/Corba cores обычная такая себе фича.
placement new C++ для замены vtbl это конечно не тот кунштюк который рекомендован для использования детям дошкольного возраста.
Просто надо знать что есть такая фича и она работает by C++ nature. Но использовать надо осознанно и по делу. Как всегда в C++ впрочем.
На войне как на войне.
E>>Да, но гарантий того, что эти указатели совпадут таки нет...
KO>Так нам их и не надо, смотри по ссылке, что я тебе давал. Там, конечно, много букв, но, от этого ещё меньше хочется всё заново повторять.
Как так не надо?
Мы же запоминаем указатель на базу первого объекта и хотим его потом использовать, как указатель на базу второго.
Насколько я понимаю, это надо для того, чтобы подменять объекты в каком-то дереве. Если нам не надо сохранять указатель, то можно не страдать, а просто завести пул объектов, например, и не париться из-за переаллокаций...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
CS>>Какая конкретно строка здесь вызывает сомнения?
R>Строка, помеченная жирным, согласно 3.8.1 порождает неопределенное поведение. Об уже этом говорил zaufi здесь
CS>Какая конкретно строка здесь вызывает сомнения?
Конкретно тут 2 помеченные
В //!!! 1 нет гарантий, что размеры объектов совпадают. Если они есть, по покажикто и как гарантирует это. Но это можно заткнуть при помощи static_assert, например...
В //!!! 2 нет гарантий, что значение выражения static_cast<Base*>( new(memory) Particular() ) совпадёт с obj...
Кроме того, мне не нравятся пустые скобки после имён типов в new.
Но сомневался я в другом.
E>>Что-то сильно я сомневаюсь, что можно так вольно обращаться с полями. В конце концов, например, деструктор класса позовёт деструктор поля, а там уже совсем и не то...
CS>На чем основаны твои сомнения?
Тут где-то был примерно такой код:
class struct {
Base Agg;
void Ups() {
new(&agg) Particular; // IMHO, так точно нехорошо делать...
}
};
И нехорошо по многим причинам сразу.
Во-первых, компилятор может что-то такое сделать в деструкторе, чего нам не понравится
Во-вторых, в пользовательском коде компилятор может быть уверен, что тип поля это Base, и не станет прибегать к виртуальным вызовам вовсе...
Так что нехорошо это всё. И главное, совершенно не понятно ради чего.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
CS>void* a = new(memory) Base();
CS>void* b = new(memory) Particular();
CS>
CS>может приводить к false этого условия
CS>
CS>(void*)a == (void*)b
CS>
Нам это условие не нужно. Нам нужно равенство указателей на Base...
А причин может быть сколько угодно. Например ОС может поддерживать, в целях безопасности, такую модель памяти, что ни при каких двух аллокациях не получается двух равных указателей...
CS>На самом деле если (void*)a != (void*)b это значит что CS>
CS>sizeof Base != sizeof Particular
CS>
Это тоже хорошо бы доказать... Например, в лэйауте базы могут быть дыры, которые как-то использует наследник...
CS>я желаю услышать также причины при которых это возможно. Классы я описал выше.
А можно чуть-чуть другие классы? А то классы выше вообще не нужны. Вместо них намного проще юзать указатели на функции...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, ononim, Вы писали:
O>Хаком тут является отсутствие вызова деструктора и отсутствие проверки необходимого размера. Все.
Это-то, как раз, фигня, потому что легко чинится.
Суть хака в том, что мы используем всё тот же указатель...
И вот это неизлечимо. А без этого хака вообще нет повода для страданий. Можно не страдать со всеми этими тонкостями, а просто иметь пул объектов и брать из пула нужный.
Тем более, что объекты без полей, так что можно иметь по статической копии каждого типа и всё...
Повторяю: ВСЁ
O>ЗЫ зря ты топик назвал 'замена VPTR' — это создает ощущение жуткого батхерта, которое и стараются передать многочисленные ужасающиеся в этом топике. Надо называть вещи своими именами — placement new на своей "куче".
Ой, а разве это "своими именами"? IMHO, в зависимости от версии обсуждаемого кода, надо или "..на чужой куче" или "...в storage другого поля". Только конретно эти стороны этого хака не важны, к сожалению... O>Для рассказывающих ужастики повторяю: ВСЕ.
А ещё крупнее слабо написать?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, ononim, Вы писали:
O>Хаком тут является отсутствие вызова деструктора и отсутствие проверки необходимого размера. Все. Для рассказывающих ужастики повторяю: ВСЕ. O>ЗЫ зря ты топик назвал 'замена VPTR' — это создает ощущение жуткого батхерта, которое и стараются передать многочисленные ужасающиеся в этом топике. Надо называть вещи своими именами — placement new на своей "куче".
+1
Флейм также отчасти спровоцирован тем, что исходный пример содержит ошибку. Участники обсуждения тем или иным образом мысленно исправляют эту ошибку и делают это по-разному. Так возникла дискуссия между мной и k.o. — здесь
O>>Хаком тут является отсутствие вызова деструктора и отсутствие проверки необходимого размера. Все. E>Это-то, как раз, фигня, потому что легко чинится. E>Суть хака в том, что мы используем всё тот же указатель...
И что? Все кучи (включая встроенную студийну) работают на "реюзании указателей". Признакомом убивания объекта для любого супероптимизирующего компилятора должен быть вызов деструктора. Которого действительно в коде топикстартера нету, но это не design flaw.
E>Тем более, что объекты без полей, так что можно иметь по статической копии каждого типа и всё... E>
Повторяю: ВСЁ
O>>ЗЫ зря ты топик назвал 'замена VPTR' — это создает ощущение жуткого батхерта, которое и стараются передать многочисленные ужасающиеся в этом топике. Надо называть вещи своими именами — placement new на своей "куче". E>Ой, а разве это "своими именами"? IMHO, в зависимости от версии обсуждаемого кода, надо или "..на чужой куче" или "...в storage другого поля". Только конретно эти стороны этого хака не важны, к сожалению...
Своими. new (p) OBJECT (args) — это Placement new. Фича языка про которую тут писали. Указатель p — это по сути свой буфер, для которого ты ручками (читай — своим аллокатором выделяешь память. Потому по сути то что сделал ТС это именно placement new и свой аллокатор. С двумя багами о которых я выше написал.
O>>Для рассказывающих ужастики повторяю: ВСЕ. E>А ещё крупнее слабо написать?
Запросто. ВСЕ
Как много веселых ребят, и все делают велосипед...
Здравствуйте, ononim, Вы писали:
E>>Суть хака в том, что мы используем всё тот же указатель... O>И что? Все кучи (включая встроенную студийну) работают на "реюзании указателей".
Беда в том, что они работают не как угодно. а на основании довольно сложных правил, описанных, например, в стандарте С++...
O>Признакомом убивания объекта для любого супероптимизирующего компилятора должен быть вызов деструктора. Которого действительно в коде топикстартера нету, но это не design flaw.
Ну а все девушки должны тебе давать. Беда в том, что они хоть по твоему и должны, но всё же не делают этого...
Да, и кстати, если должны, то ты наверное можешь показать документ, эту обязанность устанавливающий?
O>Своими. new (p) OBJECT (args) — это Placement new. Фича языка про которую тут писали. Указатель p — это по сути свой буфер, для которого ты ручками (читай — своим аллокатором выделяешь память. Потому по сути то что сделал ТС это именно placement new и свой аллокатор. С двумя багами о которых я выше написал.
1) про placement new я и не спорил и не спорю. Просто ... короче писать.
2) Про аллокатор не согласен, но главное тут не в аллокаторе.
3) Суть хака НЕ В ТОМ. Суть вот в чём. Пусть у меня есть какая-то иерархия, из экземпляров её классов собрана какая-то сложная структура данных. Ну, например, дерево, какое-то или окошки GUI'я, например.
И вот я хочу чуть подправить поведение одного из узлов этой иерархии, ничего не пересвязывая и не пересоздавая...
Вот тут на сцену обсуждаемый хак и выходит...
O>>>Для рассказывающих ужастики повторяю: ВСЕ. E>>А ещё крупнее слабо написать? O>Запросто. ВСЕ
А У МЕНЯ ДЛИНЬШЕ И ТОЛЩЕ!!!
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, k.o., Вы писали:
KO>можно черновик C++0x посмотреть, там, это почти не изменилось: здесь, тоже 12.4/4.
Спасибо за идею.
Тут смотри что пишут:
A destructor is trivial if it is neither user-provided nor deleted and if: — the destructor is not virtual,
— all of the direct base classes of its class have trivial destructors, and
— for all of the non-static data members of its class that are of class type (or array thereof), each such
class has a trivial destructor.
Otherwise, the destructor is non-trivial.
требование снижено до отсутствия виртуальности самого деструктора, по сравнению с тем, что я помню.
Интересно, как оно было раньше?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, k.o., Вы писали:
KO>>можно черновик C++0x посмотреть, там, это почти не изменилось: здесь, тоже 12.4/4.
E>Спасибо за идею. E>Тут смотри что пишут:
A destructor is trivial if it is neither user-provided nor deleted and if:
E>— the destructor is not virtual,
E>— all of the direct base classes of its class have trivial destructors, and
E>— for all of the non-static data members of its class that are of class type (or array thereof), each such
E>class has a trivial destructor.
E>Otherwise, the destructor is non-trivial.
требование снижено до отсутствия виртуальности самого деструктора, по сравнению с тем, что я помню. E>Интересно, как оно было раньше?
BFE>1. new (intf)T(); BFE>Неявно предполагается, что (Interface*)pointer == (T*)pointer
BFE>2. intf = new (intf)T(); BFE>Ещё более неявно предполагается, что (Interface*)pointer == (T*)pointer. Если T имеет множественное наследование, то через некоторое количество таких перезаказов мы можем выйти за пределы буфера.
BFE>Возражения ?
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, k.o., Вы писали:
E>>>Да, но гарантий того, что эти указатели совпадут таки нет...
KO>>Так нам их и не надо, смотри по ссылке, что я тебе давал. Там, конечно, много букв, но, от этого ещё меньше хочется всё заново повторять.
E>Как так не надо? E>Мы же запоминаем указатель на базу первого объекта и хотим его потом использовать, как указатель на базу второго. E>Насколько я понимаю, это надо для того, чтобы подменять объекты в каком-то дереве. Если нам не надо сохранять указатель, то можно не страдать, а просто завести пул объектов, например, и не париться из-за переаллокаций...
В исходном сообщении я никакого дерева не увидел, поэтому и не вижу необходимости запоминать указатель. Я, скорее, предполагал что-то вроде этого
Здравствуйте, B0FEE664, Вы писали:
BFE>Здравствуйте, k.o., Вы писали:
KO>>И что такого волшебного в malloc? Можно использовать и new char[<size of storage for any implementation>] или вообще aligned_storage<(size of storage for any implementation)>::type storage;
BFE>Можно. В C++ вполне можно написать свой аллокатор и , вероятно, за счёт дополнительного знания об объектах он будет работать быстрее стандартного.
Честно, говоря, это никак не прояняет что ты имел в виду говря
Это отличается заказом памяти. Собственно разница состоит в "malloc(<size of storage for any implementation>);"
BFE>>>А занимаясь такими вещами, мы делаем шаг в сторону С-- (здесь) или же в сторону Майкросовтовского CoCreateInstance.
KO>>Какими такими?
BFE>Ну вот когда начинают думать об объектах не в абстрактных терминах , а в терминах объёма памяти и кусках адресных пространств, вот тогда и начинается... А на выходе получается (обычно) непереносимый код. Или же что-то вроде management С++, в котором простая операция доступа через указатель (вроде a->b) в шесь раз медленнее...
Если нас интересуют только "объекты в абстрактных терминах", возможно, использование C++ не самая лучшая идея. Проще уж тогда взять C#, Java, Scala, you name it, если нужен native код есть OCaml и Haskell. А вот кодга нас интересует объём памяти и скорость работы начинается C++. ИМХО, программист на C++ должен такие вещи знать и уметь эти знания применять, как раз чтобы не получился непереносимый код работающий в 6 раз медленнее.
BFE>>>Чем, собственно, не устраивает классический подход :
KO>>Позволю себе процитировать ТС: KO>>
KO>>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их
BFE>Вот собственно это и интересно. Чем вызвано такое требование ? Это ограничение связано с реал-тайм или с контролем памяти ? Или же тут что-то ещё ? Или стандартный аллокатор настолько плох ?
Это ты меня или ТС спрашиваешь? Впрочем, стандартный аллокатор скоростью работы действительно не блещет.
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, k.o., Вы писали:
KO>>можно черновик C++0x посмотреть, там, это почти не изменилось: здесь, тоже 12.4/4.
E>Спасибо за идею. E>Тут смотри что пишут:
A destructor is trivial if it is neither user-provided nor deleted and if:
E>— the destructor is not virtual,
E>— all of the direct base classes of its class have trivial destructors, and
E>— for all of the non-static data members of its class that are of class type (or array thereof), each such
E>class has a trivial destructor.
E>Otherwise, the destructor is non-trivial.
требование снижено до отсутствия виртуальности самого деструктора, по сравнению с тем, что я помню. E>Интересно, как оно было раньше?
Как было раньше я уже писал. На самом деле, я не понимаю, как деструктор может быть виртуальным, если выполняется "if it is neither user-provided" и "all of the direct base classes of its class have trivial destructors", т.е. в этом плане, видимо, ничего не изменилось по сравнению с предыдущим стандартом.
Здравствуйте, jazzer, Вы писали:
E>>A destructor is trivial if it is neither user-provided nor deleted and if: E>>— the destructor is not virtual,
J>Ты пропустил выделенное
Да, действительно. Какой такой виртуальный деструктор, если деструктор явно вообще не описан? Тут что-то не так...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, k.o., Вы писали:
KO>В исходном сообщении я никакого дерева не увидел, поэтому и не вижу необходимости запоминать указатель. Я, скорее, предполагал что-то вроде этого
В исходном сообщении вообще не указатель, а просто встроенное в другую структуру поле...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, k.o., Вы писали:
KO>>В исходном сообщении я никакого дерева не увидел, поэтому и не вижу необходимости запоминать указатель. Я, скорее, предполагал что-то вроде этого
Здравствуйте, k.o., Вы писали:
E>>В исходном сообщении вообще не указатель, а просто встроенное в другую структуру поле...
KO>Ну да, "поле" имеющее тип абстрактного класса...
Угу. То, что пример некорректен очевидно. Не очевидна трактовка, что это типа просто new размещения в буфере...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, jazzer, Вы писали:
E>>>A destructor is trivial if it is neither user-provided nor deleted and if: E>>>— the destructor is not virtual,
J>>Ты пропустил выделенное
E>Да, действительно. Какой такой виртуальный деструктор, если деструктор явно вообще не описан? Тут что-то не так...
Сгенерированный компилятором, вестимо, если в базовом классе был виртуальный деструктор.
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, Erop, Вы писали:
E>>Здравствуйте, jazzer, Вы писали:
E>>>>A destructor is trivial if it is neither user-provided nor deleted and if: E>>>>— the destructor is not virtual,
J>>>Ты пропустил выделенное
E>>Да, действительно. Какой такой виртуальный деструктор, если деструктор явно вообще не описан? Тут что-то не так...
J>Сгенерированный компилятором, вестимо, если в базовом классе был виртуальный деструктор.
Нет, тут уже срабатывает "all of the direct base classes of its class have trivial destructors".
Здравствуйте, jazzer, Вы писали:
J>Сгенерированный компилятором, вестимо, если в базовом классе был виртуальный деструктор.
Э-э-э, тогда кто-то из баз с нетривиальным деструктором должен бы быть. И пункт как бы лишний выходит...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, gegMOPO4, Вы писали:
MOP>Сгенерированный. И унаследовавший виртуальность от предков (хотя бы одного из).
Тогда тот предок имеет нетривиальный деструктор и мы получаем, что пункт про виртуальный деструктор избыточен...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, A13x, Вы писали: A>Насчет переделки согласен, вопрос по большему счету философский. Кстати, в википедии вот пример тоже до подозрительности напоминает банальный pointer to implementation — http://en.wikipedia.org/wiki/Strategy_pattern
Стратегия и pImpl — это разные уровни. Стратегия — интерфейс, pImpl — реализация. Стратегия задаётся явно, поведение объекта может определяться несколькими стратегиями.
Вообще-то это я писал, забыл авторизироваться.
1. Не ожидал такого обилия ответов и советов
2. Многие просто не захотели заметить что я категорически НЕ могу шевелить кучу.
И плодить инстанции реализаций тоже не могу заранее.
Поэтому иду на это колдунство.
Собственно никаких конструкторов и деструкторов, тем более нетривиальных — нет.
Сейчас решение в упрощенном виде(если отбросить что все являются шаблонными классами)
приблизительно такое(собственно машина состояний и событий, но без монстроидальных switch's):
template <class T> class Interface
: public T
{
public:
template <class Q> void as() {
new ((void*)this)Q();
}
template <class Q> void as() const {
new ((void*)this)Q();
}
inline T* operator-> () { return this; }
inline T const* operator-> () const { return this; }
};
template <class Q, class T> void as(T *ptr) {
new ((void*)ptr)Q();
}
struct Intf { // на самом деле здесь template <int _код_группы>virtual R1 method1(T1, ...TN) {}
...
virtual Z1 methodZ(Q1, ...QN) {}
};
struct Derived1: public Intf { // на самом деле здесь template <int _код_группы>
// конкретная реализация
...
...
X1 methodX(U1, ...UN) {
if (params_depended_condition) { // мутирую самого себя - самый грязный HACK :)
as<DerivedK> (this);
return X1(code_X);
}
}
...
};
...прочие Derived
Где-то где нужно(в отдельной нитке):
Interface<Intf> intf;
for (;;) {
// прицеливаемсяif (cond1) intf.as<Derived1> ();
else ...
else if (condN) intf.as<DerivedN> ();
// используем реализацию
intf->method<такой-то> (параметры такие-то)
}
Думаю понятно что такой подход мне понравился из-за отсутствия таблицы-таблиц вызовов в которой черт ногу сломит.
Но терзают сомнения — на всех ли компиляторах это прокатит ?..
Здравствуйте, c-smile, Вы писали: CS>Мои эксперименты показывают profit, причем значительный если использовать технику смены vtbl для специализации. CS>Ну и код упрощается — становится более прозрачным. Немного позже смогу это выразить в цифрах если интересно. CS>Пока черновые прикидки в узких местах дают 12-16% выигрыш по скорости по сравнению с pimpl который используется в текущей версии.
Если использовать не pimpl, а impl (хранить не указатель на стратегию, а саму стратегию), то потерь из-за косвенности не будет. Кроме того, это позволит перегружать не только по display, но и по другим ортогональным стратегиям. И никаких хаков.
Здравствуйте, c-smile, Вы писали:
MOP>>Во всяком случае 12% (в узких местах) не стоят того, чтобы делать код настолько потенциально непереносимым и нерасширяемым. Есть более легальные способы борьбы за производительность.
CS>И переносим и расширяем. И 12% это много. И у всех разные критерии того что хорошо.
Я, если честно, так и не понял чего и как делает версия кода без хака.
То есть я понял, что ты собираешь некое дерево объектов, которым потом, отдельным проходом, приписываешь типы.
Это я понял, но я не понял как был устроен код, по сравнению с которым ты получил 12% прироста...
И что он вообще делает?
Судя по тому, что ты там писал, это какое-тот отображение данных. Не совсем понятно, зачем там супербыстрое переключение вообще?
Ну и не понятно что такого делают переопределяемые функции, что двойная диспечерезация их заметно тормозит.
Может надо подумать тогда о том, что вообще аиртуальные функции не нужны? Ещё 12% выиграешь...
В общем ничего не понятно.
Я бы задачу решал так, что имел бы какие-то миксины, указатели на которые и распихивал бы по узлам дерева.
А в самом дереве, или в базе узла, сделал бы NVI интерфейс, который транслирует вызовы методов, в вызовы миксина...
Казалось бы, NVI интерфейсы все подставятся, лишняя косвенность только добавится, но если это дорого, то может правда архитектуру пересмотреть в сторону юолее крупных виртуальных функций, например?
CS>Скажем если ты работаешь в Гугле и предложишь способ поднять скорость поиска на 12% это примерно значит что Гугл сможет сэкономить на железе.
О! А ты работаешь в Гугле?
CS>12% стоимости их железа это и есть профит.
В том Гугле, про который я в курсе, была фишка запускать всё на самых трешовых и бесплатных компах. Зато сразу на очень многочисленных кластерах!...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, c-smile, Вы писали:
MOP>>>Во всяком случае 12% (в узких местах) не стоят того, чтобы делать код настолько потенциально непереносимым и нерасширяемым. Есть более легальные способы борьбы за производительность.
CS>>И переносим и расширяем. И 12% это много. И у всех разные критерии того что хорошо.
E>Я, если честно, так и не понял чего и как делает версия кода без хака.
E>То есть я понял, что ты собираешь некое дерево объектов, которым потом, отдельным проходом, приписываешь типы. E>Это я понял, но я не понял как был устроен код, по сравнению с которым ты получил 12% прироста... E>И что он вообще делает?
Есть такой вариант:
static handler* pA = new handler_A();
static handler* pB = new handler_B();
class element
{
handler* ph;
void layout_width(...) { ph->do_layout_width(this, ...); }
void layout_height(...) { ph->do_layout_height(this, ...); }
void specialize(type) {
switch(type) {
case tA : ph = pA; break;
case tB : ph = pB; break; }
}
};
Что мы получаем в этом случае:
1) нет поля handler* ph; меньше памяти нужно на instance
2) нет дополнительной косвенности вызова.
3) количество параметров вызова требуемое ph->do_layout_*** меньше на единицу...
Поле handler* ph; в первом примере дублирует функционально VTBL в виде самописного лисапета.
А собственно зачем его дублировать когда оно есть и так?
E>Судя по тому, что ты там писал, это какое-тот отображение данных. Не совсем понятно, зачем там супербыстрое переключение вообще? E>Ну и не понятно что такого делают переопределяемые функции, что двойная диспечерезация их заметно тормозит. E>Может надо подумать тогда о том, что вообще аиртуальные функции не нужны? Ещё 12% выиграешь...
Вот ты смотришь на эту web страницу. Представь себе как бы ты имплементировал это всё хозяйство в C++
E>В общем ничего не понятно. E>Я бы задачу решал так, что имел бы какие-то миксины, указатели на которые и распихивал бы по узлам дерева. E>А в самом дереве, или в базе узла, сделал бы NVI интерфейс, который транслирует вызовы методов, в вызовы миксина...
Предложи вариант таких mix-ins. Как-то сложно это представить.
E>Казалось бы, NVI интерфейсы все подставятся, лишняя косвенность только добавится, но если это дорого, то может правда архитектуру пересмотреть в сторону юолее крупных виртуальных функций, например?
Архитектура и набор вызовов не с потолка взяты — определяются самой задачей — rendering HTML и SVG.
CS>>Скажем если ты работаешь в Гугле и предложишь способ поднять скорость поиска на 12% это примерно значит что Гугл сможет сэкономить на железе. E>О! А ты работаешь в Гугле?
Я работаю на себя. С Гуглом данная проблема никак не связана.
CS>>12% стоимости их железа это и есть профит. E>В том Гугле, про который я в курсе, была фишка запускать всё на самых трешовых и бесплатных компах. Зато сразу на очень многочисленных кластерах!...
Соотнеси http://www.google.com/green/ и идею понизить на 12% выхлоп CO2 от поиска.
Но это все лирика — к делу не относится.
Здравствуйте, gegMOPO4, Вы писали:
MOP>Здравствуйте, c-smile, Вы писали: CS>>Здравствуйте, gegMOPO4, Вы писали: MOP>>>Во всяком случае 12% (в узких местах) не стоят того, чтобы делать код настолько потенциально непереносимым и нерасширяемым. Есть более легальные способы борьбы за производительность. CS>>И переносим и расширяем. И 12% это много. И у всех разные критерии того что хорошо.
MOP>Эти 12% ведь были получены на синтетике? В составе большой программы, с учётом ввода/вывода/обработки, прирост будет много скромнее, на уровне погрешности (меньше 5% — можно во внимание не принимать, от сборки к сборке больше прыгать будет).
Что тебе сказать... 12% в моем случае это важно. И пять важно.
Тебе повезло что ты работаешь в бизнесе где 5% не важно и "от сборки к сборке больше прыгать" допускается.
MOP>Но если вам так уж хочется использовать здесь хак, то, пожалуйста, вынесите его в отдельный класс display_type, без данных, и реализуйте всё внутри него, как в чёрном ящике. Чтобы легче было это дело локализировать и заменить, когда посыпется.
CS>>Скажем если ты работаешь в Гугле и предложишь способ поднять скорость поиска на 12% это примерно значит что Гугл сможет сэкономить на железе. CS>>12% стоимости их железа это и есть профит.
MOP>Когда через три года Гугл захочет мигрировать на армы, или компилироваться clang-ом, или в новых процессорах x86 реализуют какую-то фишку, а gcc её применит для оптимизации, и переход затянется на год, пока будут искать баги, возникшие по вашей вине, это будет не профит.
За три года на 12% гугл сделает больше чем потребутся заплатить программеру на написание примерно этого:
(Код выше выдран из .h файла генерерумого VS при создании COM классов — там всегда генерируется как C++ так и pure С формы интерфейсов)
Кстати CONST_VTBL в COM rpc имплементации это пустая декларация ибо там возможны ситуации когда vtbl динамически преключается (и кстати вообще может генерироваться).
Но это уже так — слишком в дебри.
Еще раз: зная как технически устроенна виртуальность можно её использовать для вящей пользы.
Аккуратно, с должной оберткой и в одном месте/декларации, а не по всему коду.
Можно конечно руками залезть и тот lpVtbl поменять на то что нужно. Но имхо new(instA) B() как раз более переносимо.
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, A13x, Вы писали:
A>>Уже лень если честно писать proof-of-concept примеры, сериалы ждут.
AG>http://codepad.org/Zz3wfMF3
Надо так:
#include <iostream>
using namespace std;
struct B
{
virtual void f() { cout << "B" << endl; };
void mutate();
};
struct D : B
{
virtual void f() { cout << "D" << endl; };
};
void B::mutate()
{
new (this) D();
}
int main()
{
B* p = new B();
p->f();
p->mutate();
p->f();
return 0;
}
Здравствуйте, c-smile, Вы писали:
CS>Еще раз: зная как технически устроенна виртуальность можно её использовать для вящей пользы.
На конкретной версии конкретного компилятора, вызванного с конкретными опциями.
Ибо техническая реализация за пределами стандарта.
Это все и есть признаки "грязного хака".
Никто не запрещает грязному хаку работать в каких-то случаях (вариант UB), но от этого он грязным хаком быть не перестанет.
Когда компилятор делает виртуальный вызов, он генерит нечто вроде (на псевдо-асме):
; vptr указывает на таблицу класса А
mov ($vptr), %eax ;грузим указатель на vtbl А в регистр eax
callq 0x4(%eax) ;зовем функцию в таблице класса А
твой хак зависит от того, генерит ли компилятор "mov ($vptr), %eax" перед _каждым_ виртуальным вызовом:
; vptr указывает на таблицу класса А
mov ($vptr), %eax ;грузим указатель на vtbl А в регистр eax
callq 0x4(%eax) ;зовем одну функцию в таблице класса А
mov ($vptr), %eax ;грузим указатель на vtbl А в регистр eax
callq 0x8(%eax) ;зовем другую функцию в таблице класса A (mutate)
; теперь vptr указывает на таблицу класса В
mov ($vptr), %eax ;грузим указатель на vtbl B в регистр eax
callq 0x16(%eax) ;зовем третью функцию в таблице класса B
потому что никто не запрещает компилятору загрузить vptr только один раз (избавившись от ненужной прогулки в память за адресом vtbl) и сгенерить код наподобие:
; vptr указывает на таблицу класса А
mov ($vptr), %eax ;грузим указатель на vtbl A в регистр eax
callq 0x4(%eax) ;зовем одну функцию в таблице класса A
callq 0x8(%eax) ;зовем другую функцию в таблице класса A (mutate)
; теперь vptr указывает на таблицу класса В
callq 0x16(%eax) ;зовем третью функцию в таблице класса A
и не заметить подмены vptr, случившейся внутри вызова mutate, в результате третья функция пойдет по старой таблице.
Так что твоему хаку нужны какие-то гарантии со стороны компилятора, что именно так все и будет и этот mov вставляется перед каждым callq. У тебя такие гарантии есть?
ЗЫ Почему-то сайт не раскрашивает все комментарии в асмовском коде, только последний
ЗЗЫ Возможно, объявление volatile всех указателей на самомодифицирующиеся классы и даст какую-никакую гарантию от данной оптимизации. А может и не даcт, все это вилами на UB писано.
Здравствуйте, c-smile, Вы писали:
E>>То есть я понял, что ты собираешь некое дерево объектов, которым потом, отдельным проходом, приписываешь типы. E>>Это я понял, но я не понял как был устроен код, по сравнению с которым ты получил 12% прироста... E>>И что он вообще делает?
CS>Есть такой вариант:
CS>
CS>static handler* pA = new handler_A();
CS>static handler* pB = new handler_B();
CS>class element
CS>{
CS> handler* ph;
CS> void layout_width(...) { ph->do_layout_width(this, ...); }
CS> void layout_height(...) { ph->do_layout_height(this, ...); }
CS> void specialize(type) {
CS> switch(type) {
CS> case tA : ph = pA; break;
CS> case tB : ph = pB; break; }
CS> }
CS>};
CS>
Это примерно и полагал, но я бы немного не так сделал, конечно.
1) Я бы перечисление type не заводил бы, а просто имел бы статические handler_A, handler_B и т. д. Ссылки на на них и использовал бы, в качестве tA, tВ и т. д.
2) Я так понимаю, что у тебя класс element полиморфный? Я бы постарался сделать его обычным. Типа просто структурой.
Но я всё равно не понял главного.
1) Что такого быстрого делают do_layout_width, do_layout_height и т. д., что на лишнем косвенном вызове можно сэкономить 12%?
Может их результат просто кэшировать, например, и прямо вот при переключении типа всё и считать?
2) Судя по наличию this в вызове ph->do_layout_width(this, ...) в element есть какие-то поля. Как они переживают перевызовы конструкторов? Или конструкторы ничего в полях не инициализируют, а поля инициализируются потом? Тогда есть риск того, что какое-то поле в каком-то сценарии останется неинициализированным...
То есть это тоже очень непонятное место тут. Так как если в element нет полей, то не понятно зачем вся эта красота. А если поля есть, то не понятно как это всё работает...
CS>Функционально вышесказанное есть:
<...> CS>Что мы получаем в этом случае: CS>1) нет поля handler* ph; меньше памяти нужно на instance
Тоже как-то непонятно. Я так понимаю, что element -- это узел в каком-то дереве? Так? Тогда у него есть просто кучи данных про дерево. Кроме того, я так понял, что там ещё есть и какие-то поля данных. Таки получается, что затраты памяти мелкие довольно. Кроме того, если так уж всё жёстко, то можно element сделать просто без виртуализации.
Или это сложно сделать, потому что дерево завязано на какую-то библиотеку?
CS>2) нет дополнительной косвенности вызова.
Не понятно. По идее это дёшево очень должно быть.
CS>3) количество параметров вызова требуемое ph->do_layout_*** меньше на единицу...
А много там параметров?
Кстати, а узкое место по памяти или по производительности? Просто мне так кажется, что намного прямее оптиизировать тут контейнер, в котором дерево лежит. А не хачить...
Хотя я так пока и не понял, что там у вас на самом деле происходит...
Да и вообще вам виднее.
Но я могу поделиться там опытом печальным, что вот я некое ПО разрабатывал-разрабатывал и всё хорошо было. Ну в смысле не только я, а много народу. А потом понадобилось перенести на Мэк внезапно. А потом под линукс, а теперь жизнь вообще тяжёлая стала. Надо на телефоны всё переносить. А на телефонах такие средства разработки, что я вас умоляю...
И вот все места, где кто-то когда-то что-то на кривой козе объехал вылезли, как миленькие. Так что все такие хаки хорошо бы контролировать. Кроме того, неразумно, IMHO, выбирать такую архитектуру, которая безальтернативно навязывает хаки...
А вдруг на какой-то платформе не повезёт?
Прикинь, как будет смешно, если ты, скажем, 5-й яблофон не поддержишь, хотя 4-ре первых поддерживал
Правда, если ты гугол, то ты можешь просто разработать своё компиллер под все целевые платформы
CS>Вот ты смотришь на эту web страницу. Представь себе как бы ты имплементировал это всё хозяйство в C++
Если надо быстро, то так же, как и на С
CS>Соотнеси http://www.google.com/green/ и идею понизить на 12% выхлоп CO2 от поиска.
Я так думаю, что надо провести зелёную акцию "день без гугла"...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, gegMOPO4, Вы писали:
MOP>Хак остаётся хаком, о чём все и говорят. Вы хотя бы вынесите это в отдельное место и реализуйте нормальное безопасное решение, переключаемое условной компиляцией, чтобы легко можно было отключить. Ну и переключать так класс с данными — особенно рискованно. Сделайте, и правда, element без виртуальных функций, переключайте только handler.
Если бы коллега c-smile более понятно описал что делает его программа, то можно было бы дать более вменяемый совет.
Насколько я его понял, у него есть какой-то разборщик HTML'ек, которым он что-то разбирает, потом как-то результат разбора модифицирует, а потом это всё показывает. При этом способ показа можно переключать.
Я так понимаю, что затык идёт по скорости реендринга.
Ну, казалось бы, пишем вообще POD структуру результата разбора, потом её же модифицируем, а потом пишем к этой POD-структуре несколько визуализаторов...
Всё будет бытсро и без накладных расходов, как по памяти, так и по времени. Можно ещё, кстати, и указатели корткими сделать.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали: E>Если бы коллега c-smile более понятно описал что делает его программа, то можно было бы дать более вменяемый совет. E>Насколько я его понял, у него есть какой-то разборщик HTML'ек, которым он что-то разбирает, потом как-то результат разбора модифицирует, а потом это всё показывает. При этом способ показа можно переключать. E>Я так понимаю, что затык идёт по скорости реендринга.
Да там всё понятно. Модификация — это наложение CSS, который меняет поведение. В частности, от свойства display зависит несколько методов. Причём методы эти для большинства имплементаций тривиальные (вроде return 0). Отсюда и получаются заметные без микроскопа 12% на обработке. Не уверен, правда, заметны ли они с учётом ввода/вывода и парсинга.
E>Ну, казалось бы, пишем вообще POD структуру результата разбора, потом её же модифицируем, а потом пишем к этой POD-структуре несколько визуализаторов... E>Всё будет бытсро и без накладных расходов, как по памяти, так и по времени. Можно ещё, кстати, и указатели корткими сделать.
Ну да, можно сэмулировать виртуальные таблицы вручную. Не так «красиво» и писанины больше, зато никаких претензий со стороны стандарта и никаких подводных камней. И предложенную jazzer гипотетическую оптимизацию (которая поломала бы хаченную версию) можно не ждать от компилятора, а безопасно провести вручную, что дало бы ещё 12%. Ну, это если кровь из носу надо.
Здравствуйте, Erop, Вы писали:
E>Дык вещи вроде display можно задавть просто флажком в элементе...
Я так и не понял что ты предлагаешь. Можешь в конце концов код показать?
Я так понимаю что у тебя опыта Web разработки нет вообще. Хотя бы для себя.
Поэтому тебе сложно понять про что это все. Вариантов специализаций в современном HTML/CSS
столько что флажков не хватит. Скажем вот display property в CSS: http://www.w3schools.com/css/pr_class_display.asp
(Это примерно половина возможных враиантов)
E>Мне не понятно, он HTML-viewer что ли пишет?
Здравствуйте, Erop, Вы писали: E>Здравствуйте, gegMOPO4, Вы писали: MOP>>Да там всё понятно. Модификация — это наложение CSS, который меняет поведение. В частности, от свойства display зависит несколько методов. Причём методы эти для большинства имплементаций тривиальные (вроде return 0). Отсюда и получаются заметные без микроскопа 12% на обработке. Не уверен, правда, заметны ли они с учётом ввода/вывода и парсинга. E>Дык вещи вроде display можно задавть просто флажком в элементе...
Можно, конечно, в display просто enum хранить. Но тогда придётся делать по нему switch в каждой интересующей функции. А это не только почти наверняка медленнее виртуальных методов, но и неправильно методологически.
MOP>>Ну да, можно сэмулировать виртуальные таблицы вручную. Не так «красиво» и писанины больше, зато никаких претензий со стороны стандарта и никаких подводных камней. И предложенную jazzer гипотетическую оптимизацию (которая поломала бы хаченную версию) можно не ждать от компилятора, а безопасно провести вручную, что дало бы ещё 12%. Ну, это если кровь из носу надо. E>Да не надо никаких таблиц и вообще всех этих С++ штук. E>Ну просто на С пишем, но с контролем типов и всё
Ну да, реализация таблицы виртуальных методов на чистом C. Вот как выше для COM, только без хака. Честное и эффективное решение.
Тогда понятно. На С написать слишком сложно.
Ну тогда да, всегда есть вариант "как COM из-под С"...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, gegMOPO4, Вы писали:
MOP>Можно, конечно, в display просто enum хранить. Но тогда придётся делать по нему switch в каждой интересующей функции. А это не только почти наверняка медленнее виртуальных методов, но и неправильно методологически.
Да нет, я имел в виду не это. Насколько я понимаю большая часть всех этих виртуальных функций тривиальная. Можно просто сразу их результаты пересчитывать и в элементе кэшировать. Может получиться быстрее, однако.
MOP>Ну да, реализация таблицы виртуальных методов на чистом C. Вот как выше для COM, только без хака. Честное и эффективное решение.
Ну, например. Только я бы ещё постарался избавится от части виртуальных функций.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском