Re[5]: замена VPTR это "грязный" хак?
От: k.o. Россия  
Дата: 05.04.11 08:24
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Здравствуйте, c-smile, Вы писали:


CS>>>>Это стандартная фича языка.

Z>>>да?? а ссылочку на section в стандарте можно в студию?? %)

CS>>А "ссылочку" на что собственно?


BFE>На то, что реализация обязана использовать таблицу виртуальных методов. Откуда это следует?

BFE>А если нет , то тогда откуда следует, что можно безопасно размещать один объект по/в памяти другого объекта?

здесь
Автор: k.o.
Дата: 04.04.11
Re: замена VPTR это "грязный" хак?
От: Erop Россия  
Дата: 05.04.11 08:35
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их,

А>способ примерно такой:

А>
А>  template <class T> changeIntfRealization() {
А>    // возможные проверки на происхождение T от Interface на манер: static_assert(is_base_of<Interface, T>::value, "Ouch");
А>    new (&intf)T(); // происходит подмена VPTR
А>  }  
А>


А>Собственно вопрос в топике.


Ну при поддержке будут проблемы.

Можно сделать чуть аккуратнее и звать перед new размещения ещё и виртуальный деструктор. Тогда будет почти и не хак, кроме того, можно ещё и проверять, что места хватит на заданный класс. В общем причесать, чтобы хотя бы на необязательные грабли не наступать.
Если почти забить на проблемы с выравниванием, то получится что-то типа
template<typename IInterface, typename DefImpl = IInterface, size_t storageSize = sizeof( DefType )> class ImplSwitch {
    IInterface* impl;
    char storage[storageSize];
    template<typename NewImplType> void createImpl()
    {
        typedef char check_storage_size[ sizeof( storage ) >= sizeof( NewImplType ) ];
        assert( impl == 0 );
        impl = new(storage) NewImplType;
    }
    void freeImpl() 
    {
        if( impl != 0 ) {
            impl->~IInterface();
            impl = 0;
        }
    }
public:
    ImplSwitch() : impl( 0 ) { createImpl<DefImpl>(); }
    ~ImplSwitch() { freeImpl(); }

    operator IInterface* () { return impl; }
    operator const IInterface* () const { return impl; }

    template<typename NewImplType> SwitchTo() 
    {
        freeImpl();
        createImpl<NewImplType>();
    }
};
но это, IMHO, дело тухловатое. Ещё можно что-то похожее соорудить из буста. Только это ещё больше всё запутает.
Я не совсем понял. Есть ли в Derived1 и Derived2 ДАННЫЕ?
Если есть, то при переключении ты их теряешь, если нет, то на кой вообще всё это великолепие? Может просто обойтись полем, в котором указатель на метод/функцию лежит?

В общем, если ты расскажешь зачем такое надо, то может тебе посоветуют более традиционное решение...

Кроме того, тут как-то обсуждали в некотором роде смежный вопрос, может пригодится чего из того обсуждения
Автор: Erop
Дата: 06.03.08
...

Удачи.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: замена VPTR это "грязный" хак?
От: k.o. Россия  
Дата: 05.04.11 08:46
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Хочу использовать реализации интерфейсов без динамической реаллокации объектов реализующих их,

А>способ примерно такой:

похожее обсуждение
Автор: c-smile
Дата: 01.09.07
Re[7]: замена VPTR это "грязный" хак?
От: ononim  
Дата: 05.04.11 08:49
Оценка:
KO>>>>>Минус за то, что этот код не отвечает на вопрос ТС о грязности хака и не решает проблему смены реализации без переаллокации.
O>>>>А где вы тут видите "переаллокацию" при смене реализации? При смене там тока std::swap вызывается, который ниче не "переаллоцирует"
KO>>>Ну, хорошо, ты решил проблему переаллокации, заранее выделив память для всех используемых реализаций. Ты действительно считаешь это хорошим решением? Кстати, интересно было бы увидеть как это будет выглядеть не для 2-х реализаций, а, хотя бы для 10.
O>>Ну так а давайте писать код согласно требованиям, а не фантазиям. ТС хотел отсутствие реаллокации при смене реализации — он его получил. Если хочется минимизировать размер занятой памяти, — это уже совершенно иное требование, и решение будет тоже иное. К примеру, такое:
KO>Ещё, код (точнее идея) продемонстрированный ТС позволял использовть произвольное количество реализаций интерфейса. Применяя твой подход, получим что для этого нужно всего лишь бесконечное количество памяти, а так, конечно, никаких переаллокаций, всё согласно требованиям.
Для бесконечного количества реализаций интерфейса по любому надо бесконечное количество памяти, так что не надо считать объем сфероконя без яиц.
Как много веселых ребят, и все делают велосипед...
Re[8]: ROM, но не RAM!
От: Erop Россия  
Дата: 05.04.11 09:42
Оценка:
Здравствуйте, ononim, Вы писали:

O>Для бесконечного количества реализаций интерфейса по любому надо бесконечное количество памяти, так что не надо считать объем сфероконя без яиц.


ROM, но не RAM! А это не всегда одно и то же...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: давайте ка уже разберемся раз и навсегда!
От: zaufi Земля  
Дата: 05.04.11 10:47
Оценка: 42 (6) +1 -2
вопервых, уважаемый 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. Там низкий порог вхождения.

... не надо так расстраиваться из-за минуса... я сейчас уберу... )
Re[5]: давайте ка уже разберемся раз и навсегда!
От: k.o. Россия  
Дата: 05.04.11 11:30
Оценка:
Здравствуйте, 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:


С каких пор примеры стали нормативными?
Re[6]: замена VPTR это "грязный" хак?
От: B0FEE664  
Дата: 05.04.11 12:02
Оценка:
Здравствуйте, k.o., Вы писали:

KO>Здравствуйте, B0FEE664, Вы писали:


BFE>>Здравствуйте, c-smile, Вы писали:


CS>>>>>Это стандартная фича языка.

Z>>>>да?? а ссылочку на section в стандарте можно в студию?? %)

CS>>>А "ссылочку" на что собственно?


BFE>>На то, что реализация обязана использовать таблицу виртуальных методов. Откуда это следует?

BFE>>А если нет , то тогда откуда следует, что можно безопасно размещать один объект по/в памяти другого объекта?

KO>здесь
Автор: k.o.
Дата: 04.04.11


Хмм:

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)

Так, что ответ на subj: да, это "грязный" хак.
И каждый день — без права на ошибку...
Re[7]: замена VPTR это "грязный" хак?
От: k.o. Россия  
Дата: 05.04.11 12:14
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Здравствуйте, k.o., Вы писали:


KO>>Здравствуйте, B0FEE664, Вы писали:


BFE>>>Здравствуйте, c-smile, Вы писали:


CS>>>>>>Это стандартная фича языка.

Z>>>>>да?? а ссылочку на section в стандарте можно в студию?? %)

CS>>>>А "ссылочку" на что собственно?


BFE>>>На то, что реализация обязана использовать таблицу виртуальных методов. Откуда это следует?

BFE>>>А если нет , то тогда откуда следует, что можно безопасно размещать один объект по/в памяти другого объекта?

KO>>здесь
Автор: k.o.
Дата: 04.04.11


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);
Re[6]: давайте ка уже разберемся раз и навсегда!
От: rg45 СССР  
Дата: 05.04.11 12:20
Оценка:
Здравствуйте, 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. А откуда, собственно, уверенность в последнем?
--
Справедливость выше закона. А человечность выше справедливости.
Re[7]: давайте ка уже разберемся раз и навсегда!
От: k.o. Россия  
Дата: 05.04.11 12:46
Оценка:
Здравствуйте, 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.
Re[5]: замена VPTR это "грязный" хак?
От: rg45 СССР  
Дата: 05.04.11 12:48
Оценка:
Здравствуйте, k.o., Вы писали:

KO>>>Деструктор, если он тривиальный, вызывать необязательно


Это где такое написано? Тривиальный деструктор, сгенерированный компилятором, обеспечивает вызов деструкторов агрегированных объектов — контейнеров, строк, умных указателей и т.д. и т.п., и не вызывать его — это не правильно.
--
Справедливость выше закона. А человечность выше справедливости.
Re[8]: давайте ка уже разберемся раз и навсегда!
От: rg45 СССР  
Дата: 05.04.11 12:53
Оценка:
Здравствуйте, k.o., Вы писали:

R>>Ты говоришь, используем новый, и нет проблем, замечательно. И если можно использовать i2, то можно использовать и i1... но при условии (!), что i1 == i2. А откуда, собственно, уверенность в последнем?


KO>Скорее всего ниоткуда, только что из этого следует? Не надо использовать старое значение i1.


Правильно. Таким образом, после "подмены vptr" объект использовать не надо, ч.т.д.
--
Справедливость выше закона. А человечность выше справедливости.
Re[6]: замена VPTR это "грязный" хак?
От: k.o. Россия  
Дата: 05.04.11 12:54
Оценка:
Здравствуйте, 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.

Re[9]: давайте ка уже разберемся раз и навсегда!
От: k.o. Россия  
Дата: 05.04.11 13:00
Оценка:
Здравствуйте, 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();
Re[7]: замена VPTR это "грязный" хак?
От: rg45 СССР  
Дата: 05.04.11 13:04
Оценка:
Здравствуйте, 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.


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

  1. Ни в одном из классов в иерархии наследования не было определенных пользователем деструкторов
  2. Ни в одном из классов в иерархии наследования не было полей с нетривиальным деструктором
Ну и зачем оно такое счастье?
--
Справедливость выше закона. А человечность выше справедливости.
Re[10]: давайте ка уже разберемся раз и навсегда!
От: rg45 СССР  
Дата: 05.04.11 13:12
Оценка:
Здравствуйте, k.o., Вы писали:

R>>Правильно. Таким образом, после "подмены vptr" объект использовать не надо, ч.т.д.


KO>Смотря что понимать под выделенным. Если что-то вроде твоего примера, то не надо, если что-то другое, то вполне можно:

KO>
KO>class Interface {/*...*/};
KO>class Implementation1 : public Interface {/*...*/};
KO>class Implementation2 : public Interface {/*...*/};

KO>Interface * i1 = 0;
KO>aligned_storage<sizeof(<max implementation size>)>::type storage;

KO>// ...

KO>i1 = new (&storage) Implementation1();
i1->>foo();

KO>// ...

KO>i1 = new (&storage) Implementation2();
i1->>foo();
KO>


Ну это уже несколько другой use case, чем в исходном посте. Изначальная идея — в существующем полиморфном объекте подменить реализацию вместе с таблицей виртуальных функций и продолжать использовать тот же объект.
--
Справедливость выше закона. А человечность выше справедливости.
Re[8]: замена VPTR это "грязный" хак?
От: k.o. Россия  
Дата: 05.04.11 13:13
Оценка: 5 (1) +1
Здравствуйте, 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>
  1. Ни в одном из классов в иерархии наследования не было определенных пользователем деструкторов
    R>
  2. Ни в одном из классов в иерархии наследования не было полей с нетривиальным деструктором
    R>
R>Ну и зачем оно такое счастье?

мы всё ещё обсуждаем вот это?

ТКС>UB. Вообще, хотя бы деструктор предыдущей реализации надо вызвать перед placement new, ну и позаботиться о том, чтобы хватило памяти (sizeof(intf) >= sizeof любой из реализаций) под все реализации. И это все равно не гарантирует отсутствия проблем при оптимизации.

Деструктор, если он тривиальный, вызывать необязательно, UB будет только если размеры или выравние создаваемых объектов не соответсвуют используемой памяти.


Если да, то я нигде не призывал, отказаться от вызова деструкторов, а всего лишь выразил сомнение в том, что обязательно будет UB.
Re[11]: давайте ка уже разберемся раз и навсегда!
От: k.o. Россия  
Дата: 05.04.11 13:18
Оценка:
Здравствуйте, rg45, Вы писали:

R>Здравствуйте, k.o., Вы писали:


R>>>Правильно. Таким образом, после "подмены vptr" объект использовать не надо, ч.т.д.


KO>>Смотря что понимать под выделенным. Если что-то вроде твоего примера, то не надо, если что-то другое, то вполне можно:


R>Ну это уже несколько другой use case, чем в исходном посте. Изначальная идея — в существующем полиморфном объекте подменить реализацию вместе с таблицей виртуальных функций и продолжать использовать тот же объект.


Я такого в исходном сообщении не увидел. Мне показалось, что

Interface intf;

недвусмысленно намекает на то, что работать мы будем через интерфейс. Разумеется, тот код требует некоторого допиливания, хотя бы потому, что он не компилируется, но ТС сразу написал, что это только идея. Я, впрочем, не буду спорить, что говорить о замене vptr несколько некорректно, т.к. мы просто используем память существующего объекта для создания нового.
Re[12]: давайте ка уже разберемся раз и навсегда!
От: rg45 СССР  
Дата: 05.04.11 13:28
Оценка:
Здравствуйте, 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

Но ты уже вроде бы признал, что так делать некорректно.

А как еще можно "допилить" это пример? Если "мы просто используем память существующего объекта для создания нового", то какие типы могут иметь существующий и новый объекты?
--
Справедливость выше закона. А человечность выше справедливости.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.