Здравствуйте, 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 имеет множественное наследование, то через некоторое количество таких перезаказов мы можем выйти за пределы буфера.
Здравствуйте, 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>Возражения ?
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, A13x, Вы писали:
A>>После уже второго такого обсуждения о хаке виртуальной таблицы стало интересно — а не наткнутся ли любители переписывать таблицы виртуальных методов на грабли в виде девиртуализации проведенной оптимизирующем компилятором? J>+1. J>Одна из форм UB. J>Поэтому и нельзя реюзать указатели/ссылки на погибший объект, даже если ты там сконструировал что-то новое и вроде как ссылки/указатели остались валидными — компилятор знает, что они указывали на изначальный класс, и вправе генерить какой угодно код, исходя из этого предположения. J>Помимо девиртуализации вызова, он может, например, просто закешировать указатель на vtbl при первой инициализации указателя, а не считывать его каждый раз при обращении по указателю (зачем делать два джампа, когда достаточно одного?) и таким образом продолжить обращаться к старой таблице даже после подмены, с сопутствующими фейерверками.
Здравствуйте, 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 — реализация. Стратегия задаётся явно, поведение объекта может определяться несколькими стратегиями.
Здравствуйте, Erop, Вы писали:
E>Здравствуйте, gegMOPO4, Вы писали:
MOP>>Сгенерированный. И унаследовавший виртуальность от предков (хотя бы одного из).
E>Тогда тот предок имеет нетривиальный деструктор и мы получаем, что пункт про виртуальный деструктор избыточен...
ну в С++0х можно явно дефолтные версии генерить...
Я не уверен, но вполне возможно, что можно написать так:
struct A
{
virtual ~A() = default;
};
тут он и виртуальный, и в то же время не определен пользователем
Вообще-то это я писал, забыл авторизироваться.
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<такой-то> (параметры такие-то)
}
Думаю понятно что такой подход мне понравился из-за отсутствия таблицы-таблиц вызовов в которой черт ногу сломит.
Но терзают сомнения — на всех ли компиляторах это прокатит ?..
Здравствуйте, Seigram, Вы писали: S>1. Не ожидал такого обилия ответов и советов
А теперь их прочитайте. Причём особенно внимательно не те, что кричат «да, конечно можно!», а те, которые объясняют, почему так делать нельзя, чем это грозит и как сделать, чтобы было можно.
S>2. Многие просто не захотели заметить что я категорически НЕ могу шевелить кучу.
Решения без кучи предлагали. Заведите в мутирующем классе сырой буффер достаточного размера с запасом на выравнивание (char[...]), указатель на интерфейс и потом pimpl=new(area)T. Обязателен статический или динамический assert.
S> И плодить инстанции реализаций тоже не могу заранее.
Если у вас в производных классах добавляются новые члены-данные, то фейл очевиден. Если используются только данные из исходного мутирующего класса (или их вообще нет), то для чистых стратегий без данных достаточно одного инстанса на класс (и содержать он будет только VPTR). Код шаблона, хакающего VPTR, будет больше.
S>Но терзают сомнения — на всех ли компиляторах это прокатит ?..
Как уже говорили выше, могут быть проблемы при инициализации (указатели на разные классы могут не совпадать, выравнивание, размер), использовании (статическая девиртуализация или кеширование в рантайме), удалении (дефолный деструктор может делать неожиданные вещи). Поэтому не делайте так.